Use OAuth to authorize with Mastodon API

This commit is contained in:
Christian Muehlhaeuser 2019-05-05 13:41:07 +02:00
parent c4f5332b31
commit fe0ac8c5eb
No known key found for this signature in database
GPG key ID: 3CF9FA45CA1EBB7E
6 changed files with 210 additions and 36 deletions

View file

@ -20,13 +20,8 @@ const (
// Account is a Mastodon account for Chirp. // Account is a Mastodon account for Chirp.
type Account struct { type Account struct {
username string
password string
instance string
clientID string
clientSecret string
client *mastodon.Client client *mastodon.Client
config *mastodon.Config
self *mastodon.Account self *mastodon.Account
evchan chan interface{} evchan chan interface{}
@ -34,30 +29,54 @@ type Account struct {
} }
// NewAccount returns a new Mastodon account. // NewAccount returns a new Mastodon account.
func NewAccount(username, password, instance, clientID, clientSecret string) *Account { func NewAccount(instance, token, clientID, clientSecret string) *Account {
return &Account{ mconfig := &mastodon.Config{
username: username, Server: instance,
password: password, AccessToken: token,
instance: instance, ClientID: clientID,
clientID: clientID, ClientSecret: clientSecret,
clientSecret: clientSecret,
} }
return &Account{
config: mconfig,
client: mastodon.NewClient(mconfig),
}
}
func RegisterAccount(instance string) (*Account, string, string, error) {
app, err := mastodon.RegisterApp(context.Background(), &mastodon.AppConfig{
Server: instance,
ClientName: "Telephant",
Scopes: "read write follow post",
Website: "",
})
if err != nil {
return nil, "", "", err
}
a := NewAccount(instance, "", app.ClientID, app.ClientSecret)
return a, app.AuthURI, app.RedirectURI, nil
}
func (mod *Account) Authenticate(code string) (string, string, string, string, error) {
err := mod.client.AuthenticateToken(context.Background(), code, "urn:ietf:wg:oauth:2.0:oob")
if err != nil {
return "", "", "", "", err
}
return mod.config.Server, mod.config.AccessToken, mod.config.ClientID, mod.config.ClientSecret, nil
} }
// Run executes the account's event loop. // Run executes the account's event loop.
func (mod *Account) Run(eventChan chan interface{}) { func (mod *Account) Run(eventChan chan interface{}) {
mod.evchan = eventChan mod.evchan = eventChan
mod.client = mastodon.NewClient(&mastodon.Config{ if mod.config.AccessToken == "" {
Server: mod.instance, return
ClientID: mod.clientID,
ClientSecret: mod.clientSecret,
})
err := mod.client.Authenticate(context.Background(), mod.username, mod.password)
if err != nil {
panic(err)
} }
var err error
mod.self, err = mod.client.GetAccountCurrentUser(context.Background()) mod.self, err = mod.client.GetAccountCurrentUser(context.Background())
if err != nil { if err != nil {
panic(err) panic(err)
@ -75,6 +94,10 @@ func (mod *Account) Run(eventChan chan interface{}) {
mod.handleStream() mod.handleStream()
} }
func (mod *Account) Logo() string {
return "mastodon.svg"
}
// Post posts a new status // Post posts a new status
func (mod *Account) Post(message string) error { func (mod *Account) Post(message string) error {
_, err := mod.client.PostStatus(context.Background(), &mastodon.Toot{ _, err := mod.client.PostStatus(context.Background(), &mastodon.Toot{

View file

@ -9,6 +9,9 @@ import (
type UIBridge struct { type UIBridge struct {
core.QObject core.QObject
_ func(instance string) `slot:"connectButton"`
_ func(instance string) `slot:"authButton"`
_ func(replyid string, message string) `slot:"postButton"` _ func(replyid string, message string) `slot:"postButton"`
_ func(id string) `slot:"shareButton"` _ func(id string) `slot:"shareButton"`
_ func(id string) `slot:"likeButton"` _ func(id string) `slot:"likeButton"`
@ -52,6 +55,8 @@ func setupQmlBridges() {
accountBridge.SetUsername("Chirp!") accountBridge.SetUsername("Chirp!")
uiBridge = NewUIBridge(nil) uiBridge = NewUIBridge(nil)
uiBridge.ConnectConnectButton(connectToInstance)
uiBridge.ConnectAuthButton(authInstance)
uiBridge.ConnectPostButton(reply) uiBridge.ConnectPostButton(reply)
uiBridge.ConnectShareButton(share) uiBridge.ConnectShareButton(share)
uiBridge.ConnectLikeButton(like) uiBridge.ConnectLikeButton(like)

View file

@ -1,6 +1,7 @@
package main package main
import ( import (
"fmt"
"log" "log"
"os" "os"
@ -12,6 +13,35 @@ import (
"github.com/muesli/chirp/accounts/mastodon" "github.com/muesli/chirp/accounts/mastodon"
) )
var (
config Config
)
func connectToInstance(instance string) {
var authURI string
var redirectURI string
var err error
tc, authURI, redirectURI, err = mastodon.RegisterAccount(instance)
fmt.Println("auth uri:", authURI)
// fmt.Println("redirect uri:", redirectURI)
fmt.Println(err)
}
func authInstance(code string) {
instance, token, clientID, clientSecret, err := tc.Authenticate(code)
fmt.Println("authenticate:", err)
if err != nil {
return
}
config.Account[0].Instance = instance
config.Account[0].ClientID = clientID
config.Account[0].ClientSecret = clientSecret
config.Account[0].Token = token
setupMastodon(config.Account[0])
}
// reply is used to post a new message // reply is used to post a new message
// if replyid is > 0, it's send as a reply // if replyid is > 0, it's send as a reply
func reply(replyid string, message string) { func reply(replyid string, message string) {
@ -59,7 +89,7 @@ func runApp(config Config) {
// setupMastodon starts a new Mastodon client and sets up event handling & models for it // setupMastodon starts a new Mastodon client and sets up event handling & models for it
func setupMastodon(config Account) { func setupMastodon(config Account) {
tc = mastodon.NewAccount(config.Username, config.Password, config.Instance, config.ClientID, config.ClientSecret) tc = mastodon.NewAccount(config.Instance, config.Token, config.ClientID, config.ClientSecret)
postModel := NewMessageModel(nil) postModel := NewMessageModel(nil)
notificationModel := NewMessageModel(nil) notificationModel := NewMessageModel(nil)
@ -83,7 +113,7 @@ func main() {
setupQmlBridges() setupQmlBridges()
// load config // load config
config := LoadConfig() config = LoadConfig()
if config.Style == "" { if config.Style == "" {
config.Style = "Material" config.Style = "Material"
} }

View file

@ -16,10 +16,10 @@ type Account struct {
AccessTokenSecret string AccessTokenSecret string
*/ */
Instance string Instance string
Username string
Password string
ClientID string ClientID string
ClientSecret string ClientSecret string
Token string
RedirectURI string
} }
// Config holds chirp's config settings // Config holds chirp's config settings
@ -37,18 +37,10 @@ func LoadConfig() Config {
_, err := os.Stat(configFile) _, err := os.Stat(configFile)
if err != nil { if err != nil {
SaveConfig(Config{ SaveConfig(Config{
Style: "Material", Style: "Material",
Account: []Account{ Account: []Account{Account{}},
{
Instance: "your instance",
Username: "your username",
Password: "your password",
ClientID: "your client id",
ClientSecret: "your client secret",
},
},
}) })
log.Fatal("Config file is missing, but a template was created for you! Please edit ", configFile) //log.Fatal("Config file is missing, but a template was created for you! Please edit ", configFile)
} }
var config Config var config Config

116
qml/ConnectDialog.qml Normal file
View file

@ -0,0 +1,116 @@
import QtQuick 2.4
import QtQuick.Controls 2.1
import QtQuick.Controls.Material 2.1
import QtQuick.Layouts 1.3
Popup {
id: connectDialog
property string instance
modal: true
focus: true
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent
ColumnLayout {
spacing: 16
anchors.fill: parent
clip: true
Label {
text: qsTr("Add an Account")
Layout.alignment: Qt.AlignHCenter
font.bold: true
}
Image {
id: logo
Layout.alignment: Qt.AlignHCenter
smooth: true
source: "images/accounts/mastodon.svg"
sourceSize.height: 128
}
SwipeView {
id: connectSwipeView
Layout.fillWidth: true
Layout.fillHeight: true
Layout.alignment: Qt.AlignHCenter
Component.onCompleted: contentItem.interactive = false
currentIndex: 0
Item {
id: instancePage
ColumnLayout {
anchors.fill: parent
TextArea {
id: instanceArea
focus: true
selectByMouse: true
placeholderText: qsTr("Instance, e.g. https://mastodon.social")
Layout.fillWidth: true
}
Button {
id: connectButton
enabled: instanceArea.text.length > 0
Layout.alignment: Qt.AlignBottom | Qt.AlignCenter
highlighted: true
Material.accent: Material.Blue
text: qsTr("Authorize Telephant")
onClicked: {
var instance = instanceArea.text
connectSwipeView.currentIndex = 1
uiBridge.connectButton(instance)
}
}
}
}
Item {
id: authPage
ColumnLayout {
anchors.fill: parent
TextArea {
id: codeArea
focus: true
selectByMouse: true
placeholderText: qsTr("Auth code provided by your instance")
Layout.fillWidth: true
}
Button {
id: authButton
enabled: codeArea.text.length > 0
Layout.alignment: Qt.AlignBottom | Qt.AlignCenter
highlighted: true
Material.accent: Material.Blue
text: qsTr("Login")
onClicked: {
var code = codeArea.text
connectDialog.close()
uiBridge.authButton(code)
}
}
}
}
}
PageIndicator {
id: indicator
Layout.alignment: Qt.AlignHCenter
count: connectSwipeView.count
currentIndex: connectSwipeView.currentIndex
// anchors.bottom: connectSwipeView.bottom
// anchors.horizontalCenter: parent.horizontalCenter
}
}
}

View file

@ -29,6 +29,14 @@ ApplicationWindow {
width: Math.min(mainWindow.width, mainWindow.height) / 3 * 2 width: Math.min(mainWindow.width, mainWindow.height) / 3 * 2
} }
ConnectDialog {
id: connectDialog
x: mainWindow.width / 2 - width / 2
y: mainWindow.height / 2 - height / 2 - mainWindow.header.height / 2
width: 340
height: 340
}
SettingsDialog { SettingsDialog {
id: settingsDialog id: settingsDialog
x: (mainWindow.width - width) / 2 x: (mainWindow.width - width) / 2