add token creation

This commit is contained in:
FloatingGhost 2022-12-16 05:08:06 +00:00
parent ac2830ba0d
commit ad3ecadfb1
9 changed files with 257 additions and 5 deletions

2
Makefile Normal file
View File

@ -0,0 +1,2 @@
all:
go build

33
README.md Normal file
View File

@ -0,0 +1,33 @@
# Constanze
A little cli tool to help you interact with akkoma instances.
Helpful when dealing with operations that are not yet supported by the web interface.
## Installation
download the latest release from the [releases page](./releases) and put it somewhere in your path.
## Usage
Use `./constanze --help` to get a list of all available commands.
### Configuration
A basic configuration file can be generated with:
```bash
./constanze configure
```
### Creating a new oauth application
```bash
./constanze token --client-app --scopes "read write" --client-name "My App"
```
### Debug mode
If you want to see the requests and responses, you can enable debug mode with:
```bash
./constanze --debug <command>
```

View File

@ -1,9 +1,78 @@
package main
import "github.com/urfave/cli/v2"
import (
"github.com/pkg/browser"
log "github.com/sirupsen/logrus"
"github.com/urfave/cli/v2"
)
func setDebugLogging(cCtx *cli.Context) {
if cCtx.Bool("debug") {
log.SetLevel(log.DebugLevel)
log.Debug("Debug logging enabled")
} else {
log.SetLevel(log.InfoLevel)
}
}
func Configure(cCtx *cli.Context) error {
setDebugLogging(cCtx)
instanceUrl := ReadLine("Instance URL:")
config := Config{InstanceUrl: instanceUrl}
return SaveConfigFromContext(cCtx, config)
}
func Login(cCtx *cli.Context) error {
setDebugLogging(cCtx)
config, err := LoadConfigFromContext(cCtx)
if err != nil {
return err
}
scopes := cCtx.String("scopes")
clientApp := cCtx.Bool("client-app")
clientName := cCtx.String("client-name")
log.Info("Creating OAuth app")
oauthApp, err := CreateOAuthApp(config.InstanceUrl, clientName, scopes)
if err != nil {
return err
}
config.OAuthApp = oauthApp
if !clientApp {
err = SaveConfigFromContext(cCtx, config)
if err != nil {
return err
}
}
log.Info("OAuth app created")
log.Info("Opening browser")
authUrl, err := AuthorizeUrl(config.InstanceUrl, config.OAuthApp, cCtx.String("scopes"))
if err != nil {
return err
}
log.Info("Auth URL: ", authUrl)
err = browser.OpenURL(authUrl)
if err != nil {
log.Infof("Could not open browser, please open the following URL in your browser: %s", authUrl)
}
authCode := ReadLine("Auth code:")
oauthToken, err := RequestToken(config.InstanceUrl, config.OAuthApp, scopes, authCode)
if err != nil {
return err
}
config.OAuthToken = oauthToken
if !clientApp {
err = SaveConfigFromContext(cCtx, config)
if err != nil {
return err
}
}
if clientApp {
log.Info("Client app token:")
log.Infof("%s %s", oauthToken.TokenType, oauthToken.AccessToken)
}
return nil
}

View File

@ -10,7 +10,9 @@ import (
)
type Config struct {
InstanceUrl string `yaml:"instance_url"`
InstanceUrl string `yaml:"instance_url"`
OAuthApp OAuthApp `yaml:"oauth_app"`
OAuthToken OAuthToken `yaml:"oauth_token"`
}
func LoadConfig(configLocation string) (Config, error) {

4
go.mod
View File

@ -4,6 +4,8 @@ go 1.19
require (
github.com/go-playground/validator/v10 v10.11.1
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8
github.com/sirupsen/logrus v1.9.0
github.com/urfave/cli/v2 v2.23.7
gopkg.in/yaml.v2 v2.4.0
)
@ -16,6 +18,6 @@ require (
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 // indirect
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 // indirect
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect
golang.org/x/text v0.3.7 // indirect
)

8
go.sum
View File

@ -22,6 +22,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@ -30,6 +32,8 @@ github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUA
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
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/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
@ -44,8 +48,10 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 h1:siQdpVirKtzPhKl3lZWozZraCFObP8S1v6PRp0bLrtU=
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=

23
http.go Normal file
View File

@ -0,0 +1,23 @@
package main
import (
"bytes"
"encoding/json"
log "github.com/sirupsen/logrus"
"net/http"
)
func Post(url string, body interface{}) (*http.Response, error) {
log.Debugf("POST %s", url)
log.Debugf("Body: %v", body)
jsonBody, err := json.Marshal(body)
if err != nil {
return nil, err
}
resp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonBody))
log.Debugf("Status: %d", resp.StatusCode)
if err != nil {
return nil, err
}
return resp, nil
}

30
main.go
View File

@ -1,8 +1,8 @@
package main
import (
log "github.com/sirupsen/logrus"
"github.com/urfave/cli/v2"
"log"
"os"
)
@ -17,6 +17,11 @@ func main() {
Value: "config.yml",
Usage: "Point to your config file",
},
&cli.BoolFlag{
Name: "debug",
Value: false,
Usage: "Enable debug logging",
},
},
}
@ -32,5 +37,28 @@ func commands() []*cli.Command {
Usage: "Set up costanze",
Action: Configure,
},
{
Name: "login",
Usage: "Log in to your instance",
Aliases: []string{"token"},
Action: Login,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "scopes",
Value: "read write follow push",
Usage: "Scopes to request",
},
&cli.BoolFlag{
Name: "client-app",
Value: false,
Usage: "Create a new oauth app, but just print out the credentials for later use",
},
&cli.StringFlag{
Name: "client-name",
Value: "costanze",
Usage: "Name of the client app",
},
},
},
}
}

87
oauth.go Normal file
View File

@ -0,0 +1,87 @@
package main
import (
"encoding/json"
log "github.com/sirupsen/logrus"
"io"
"net/url"
)
type OAuthApp struct {
ClientId string `json:"client_id" yaml:"client_id"`
ClientSecret string `json:"client_secret" yaml:"client_secret"`
}
type OAuthToken struct {
AccessToken string `json:"access_token" yaml:"access_token"`
Me string `json:"me" yaml:"me"`
RefreshToken string `json:"refresh_token" yaml:"refresh_token"`
Scope string `json:"scope" yaml:"scope"`
TokenType string `json:"token_type" yaml:"token_type"`
}
func CreateOAuthApp(instanceUrl string, clientName string, scopes string) (OAuthApp, error) {
uri, err := url.JoinPath(instanceUrl, "/api/v1/apps")
if err != nil {
return OAuthApp{}, err
}
request := map[string]interface{}{
"client_name": clientName,
"redirect_uris": "urn:ietf:wg:oauth:2.0:oob",
"scopes": scopes,
}
response, err := Post(uri, request)
if err != nil {
return OAuthApp{}, err
}
body, err := io.ReadAll(response.Body)
app := &OAuthApp{}
err = json.Unmarshal(body, app)
if err != nil {
return OAuthApp{}, err
}
return *app, nil
}
func RequestToken(instanceUrl string, oauthApp OAuthApp, scopes string, code string) (OAuthToken, error) {
log.Debug("Requesting token")
args := map[string]interface{}{
"client_id": oauthApp.ClientId,
"client_secret": oauthApp.ClientSecret,
"code": code,
"grant_type": "authorization_code",
"redirect_uri": "urn:ietf:wg:oauth:2.0:oob",
"scope": scopes,
}
tokenUrl, err := url.JoinPath(instanceUrl, "/oauth/token")
if err != nil {
return OAuthToken{}, err
}
response, err := Post(tokenUrl, args)
if err != nil {
return OAuthToken{}, err
}
body, err := io.ReadAll(response.Body)
token := &OAuthToken{}
err = json.Unmarshal(body, token)
if err != nil {
return OAuthToken{}, err
}
return *token, nil
}
func AuthorizeUrl(instanceUrl string, oauthApp OAuthApp, scopes string) (string, error) {
args := url.Values{
"client_id": {oauthApp.ClientId},
"redirect_uri": {"urn:ietf:wg:oauth:2.0:oob"},
"response_type": {"code"},
"scope": {scopes},
}.Encode()
authUrl, err := url.JoinPath(instanceUrl, "/oauth/authorize")
if err != nil {
return "", err
}
return authUrl + "?" + args, nil
}