mirror of
https://github.com/diamondburned/cchat-discord.git
synced 2024-11-22 06:02:59 +00:00
changed Authenticator impl to the new cchat API
This commit is contained in:
parent
7ce513cf68
commit
31c378db5a
|
@ -24,10 +24,7 @@ func (Service) Name() text.Rich {
|
|||
}
|
||||
|
||||
func (Service) Authenticate() []cchat.Authenticator {
|
||||
return []cchat.Authenticator{
|
||||
authenticate.New(),
|
||||
authenticate.NewDiscordLogin(),
|
||||
}
|
||||
return authenticate.FirstStageAuthenticators()
|
||||
}
|
||||
|
||||
func (Service) AsIconer() cchat.Iconer {
|
||||
|
|
2
go.mod
2
go.mod
|
@ -4,7 +4,7 @@ go 1.14
|
|||
|
||||
require (
|
||||
github.com/diamondburned/arikawa v1.3.6
|
||||
github.com/diamondburned/cchat v0.3.8
|
||||
github.com/diamondburned/cchat v0.3.11
|
||||
github.com/diamondburned/ningen v0.2.1-0.20201023061015-ce64ffb0bb12
|
||||
github.com/dustin/go-humanize v1.0.0
|
||||
github.com/go-test/deep v1.0.7
|
||||
|
|
2
go.sum
2
go.sum
|
@ -101,6 +101,8 @@ github.com/diamondburned/cchat v0.3.7 h1:0t3FkbzC/pBRAR3w0uYznJ+7dYqcR1M48a9wgz4
|
|||
github.com/diamondburned/cchat v0.3.7/go.mod h1:IlMtF+XIvAJh0GL/2yFdf0/34w+Hdy5A1GgvSwAXtQI=
|
||||
github.com/diamondburned/cchat v0.3.8 h1:vgFe8giVfwsAO+WpTYsTDIXvRUN48osVPNu0pZNvPEk=
|
||||
github.com/diamondburned/cchat v0.3.8/go.mod h1:IlMtF+XIvAJh0GL/2yFdf0/34w+Hdy5A1GgvSwAXtQI=
|
||||
github.com/diamondburned/cchat v0.3.11 h1:C1f9Tp7Kz3t+T1SlepL1RS7b/kACAKWAIZXAgJEpCHg=
|
||||
github.com/diamondburned/cchat v0.3.11/go.mod h1:IlMtF+XIvAJh0GL/2yFdf0/34w+Hdy5A1GgvSwAXtQI=
|
||||
github.com/diamondburned/ningen v0.1.1-0.20200621014632-6babb812b249 h1:yP7kJ+xCGpDz6XbcfACJcju4SH1XDPwlrvbofz3lP8I=
|
||||
github.com/diamondburned/ningen v0.1.1-0.20200621014632-6babb812b249/go.mod h1:xW9hpBZsGi8KpAh10TyP+YQlYBo+Xc+2w4TR6N0951A=
|
||||
github.com/diamondburned/ningen v0.1.1-0.20200708085949-b64e350f3b8c h1:3h/kyk6HplYZF3zLi106itjYJWjbuMK/twijeGLEy2M=
|
||||
|
|
|
@ -4,8 +4,6 @@ import (
|
|||
"errors"
|
||||
|
||||
"github.com/diamondburned/cchat"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/session"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/state"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -13,105 +11,12 @@ var (
|
|||
EnterPassword = errors.New("enter your password")
|
||||
)
|
||||
|
||||
type Authenticator struct {
|
||||
username string
|
||||
password string
|
||||
}
|
||||
|
||||
func New() cchat.Authenticator {
|
||||
return &Authenticator{}
|
||||
}
|
||||
|
||||
func (a *Authenticator) stage() int {
|
||||
switch {
|
||||
// Stage 1: Prompt for the token OR username.
|
||||
case a.username == "" && a.password == "":
|
||||
return 0
|
||||
|
||||
// Stage 2: Prompt for the password.
|
||||
case a.password == "":
|
||||
return 1
|
||||
|
||||
// Stage 3: Prompt for the TOTP token.
|
||||
default:
|
||||
return 2
|
||||
// FirstStageAuthenticators constructs a slice of newly made first stage
|
||||
// authenticators.
|
||||
func FirstStageAuthenticators() []cchat.Authenticator {
|
||||
return []cchat.Authenticator{
|
||||
NewTokenAuthenticator(),
|
||||
NewLoginAuthenticator(),
|
||||
NewDiscordLogin(),
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Authenticator) AuthenticateForm() []cchat.AuthenticateEntry {
|
||||
switch a.stage() {
|
||||
case 0:
|
||||
return []cchat.AuthenticateEntry{
|
||||
{Name: "Token", Secret: true},
|
||||
{Name: "Username", Description: "Fill either Token or Username only."},
|
||||
}
|
||||
case 1:
|
||||
return []cchat.AuthenticateEntry{
|
||||
{Name: "Password", Secret: true},
|
||||
}
|
||||
case 2:
|
||||
return []cchat.AuthenticateEntry{
|
||||
{Name: "Auth Code", Description: "6-digit code for Two-factor Authentication."},
|
||||
}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Authenticator) Authenticate(form []string) (cchat.Session, error) {
|
||||
switch a.stage() {
|
||||
case 0:
|
||||
if len(form) != 2 {
|
||||
return nil, ErrMalformed
|
||||
}
|
||||
|
||||
switch {
|
||||
case form[0] != "": // Token
|
||||
i, err := state.NewFromToken(form[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return session.NewFromInstance(i)
|
||||
|
||||
case form[1] != "": // Username
|
||||
// Move to a new stage.
|
||||
a.username = form[1]
|
||||
return nil, EnterPassword
|
||||
}
|
||||
|
||||
case 1:
|
||||
if len(form) != 1 {
|
||||
return nil, ErrMalformed
|
||||
}
|
||||
|
||||
a.password = form[0]
|
||||
|
||||
i, err := state.Login(a.username, a.password, "")
|
||||
if err != nil {
|
||||
// If the error is not ErrMFA, then we should reset password to
|
||||
// empty.
|
||||
if !errors.Is(err, session.ErrMFA) {
|
||||
a.password = ""
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return session.NewFromInstance(i)
|
||||
|
||||
case 2:
|
||||
if len(form) != 1 {
|
||||
return nil, ErrMalformed
|
||||
}
|
||||
|
||||
i, err := state.Login(a.username, a.password, form[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return session.NewFromInstance(i)
|
||||
}
|
||||
|
||||
return nil, ErrMalformed
|
||||
}
|
||||
|
|
|
@ -8,29 +8,41 @@ import (
|
|||
"github.com/diamondburned/cchat"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/session"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/state"
|
||||
"github.com/diamondburned/cchat/text"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/skratchdot/open-golang/open"
|
||||
)
|
||||
|
||||
var ErrDLNotFound = errors.New("DiscordLogin not found. Please install it from the GitHub page.")
|
||||
|
||||
// DiscordLoginAuth is a first stage authenticator that allows the user to
|
||||
// authenticate using DiscordLogin. The Authenticate() function will exec up the
|
||||
// application if possible. If not, it'll try and exec up a browser.
|
||||
type DiscordLoginAuth struct{}
|
||||
|
||||
func NewDiscordLogin() cchat.Authenticator {
|
||||
return DiscordLoginAuth{}
|
||||
}
|
||||
|
||||
func (DiscordLoginAuth) Name() text.Rich {
|
||||
return text.Plain("DiscordLogin")
|
||||
}
|
||||
|
||||
func (DiscordLoginAuth) Description() text.Rich {
|
||||
return text.Plain("Log in using DiscordLogin, a WebKit application.")
|
||||
}
|
||||
|
||||
// AuthenticateForm returns an empty slice.
|
||||
func (DiscordLoginAuth) AuthenticateForm() []cchat.AuthenticateEntry {
|
||||
return []cchat.AuthenticateEntry{}
|
||||
}
|
||||
|
||||
// Authenticate pops up discordlogin.
|
||||
func (DiscordLoginAuth) Authenticate([]string) (cchat.Session, error) {
|
||||
// Authenticate pops up DiscordLogin.
|
||||
func (DiscordLoginAuth) Authenticate([]string) (cchat.Session, cchat.AuthenticateError) {
|
||||
path, err := lookPathExtras("discordlogin")
|
||||
if err != nil {
|
||||
openDiscordLoginPage()
|
||||
return nil, ErrDLNotFound
|
||||
return nil, cchat.WrapAuthenticateError(ErrDLNotFound)
|
||||
}
|
||||
|
||||
cmd := &exec.Cmd{Path: path}
|
||||
|
@ -40,19 +52,26 @@ func (DiscordLoginAuth) Authenticate([]string) (cchat.Session, error) {
|
|||
|
||||
b, err := cmd.Output()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "DiscordLogin failed")
|
||||
return nil, cchat.WrapAuthenticateError(errors.Wrap(err, "DiscordLogin failed"))
|
||||
}
|
||||
|
||||
if len(b) == 0 {
|
||||
return nil, errors.New("DiscordLogin returned nothing, check Console.")
|
||||
return nil, cchat.WrapAuthenticateError(
|
||||
errors.New("DiscordLogin returned nothing, check Console."),
|
||||
)
|
||||
}
|
||||
|
||||
i, err := state.NewFromToken(string(b))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, cchat.WrapAuthenticateError(errors.Wrap(err, "failed to use token"))
|
||||
}
|
||||
|
||||
return session.NewFromInstance(i)
|
||||
s, err := session.NewFromInstance(i)
|
||||
if err != nil {
|
||||
return nil, cchat.WrapAuthenticateError(errors.Wrap(err, "failed to make a session"))
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func openDiscordLoginPage() {
|
||||
|
|
65
internal/discord/authenticate/login.go
Normal file
65
internal/discord/authenticate/login.go
Normal file
|
@ -0,0 +1,65 @@
|
|||
package authenticate
|
||||
|
||||
import (
|
||||
"github.com/diamondburned/arikawa/api"
|
||||
"github.com/diamondburned/cchat"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/session"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/state"
|
||||
"github.com/diamondburned/cchat/text"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// LoginAuthenticator is a first stage authenticator that allows the user to
|
||||
// authenticate using their email and password.
|
||||
type LoginAuthenticator struct {
|
||||
client *api.Client
|
||||
}
|
||||
|
||||
func NewLoginAuthenticator() cchat.Authenticator {
|
||||
return &LoginAuthenticator{
|
||||
client: api.NewClient(""),
|
||||
}
|
||||
}
|
||||
|
||||
func (a *LoginAuthenticator) Name() text.Rich {
|
||||
return text.Plain("Email")
|
||||
}
|
||||
|
||||
func (a *LoginAuthenticator) Description() text.Rich {
|
||||
return text.Plain("Log in using your email.")
|
||||
}
|
||||
|
||||
func (a *LoginAuthenticator) AuthenticateForm() []cchat.AuthenticateEntry {
|
||||
return []cchat.AuthenticateEntry{
|
||||
{Name: "Email"},
|
||||
{Name: "Password", Secret: true},
|
||||
}
|
||||
}
|
||||
|
||||
func (a *LoginAuthenticator) Authenticate(form []string) (cchat.Session, cchat.AuthenticateError) {
|
||||
if len(form) != 2 {
|
||||
return nil, cchat.WrapAuthenticateError(ErrMalformed)
|
||||
}
|
||||
|
||||
// Try to login without TOTP
|
||||
l, err := a.client.Login(form[0], form[1])
|
||||
if err != nil {
|
||||
return nil, cchat.WrapAuthenticateError(errors.Wrap(err, "failed to login"))
|
||||
}
|
||||
|
||||
if l.MFA {
|
||||
return nil, &ErrNeeds2FA{loginResp: l}
|
||||
}
|
||||
|
||||
i, err := state.NewFromToken(l.Token)
|
||||
if err != nil {
|
||||
return nil, cchat.WrapAuthenticateError(errors.Wrap(err, "failed to use token"))
|
||||
}
|
||||
|
||||
s, err := session.NewFromInstance(i)
|
||||
if err != nil {
|
||||
return nil, cchat.WrapAuthenticateError(errors.Wrap(err, "failed to make a session"))
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
49
internal/discord/authenticate/token.go
Normal file
49
internal/discord/authenticate/token.go
Normal file
|
@ -0,0 +1,49 @@
|
|||
package authenticate
|
||||
|
||||
import (
|
||||
"github.com/diamondburned/cchat"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/session"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/state"
|
||||
"github.com/diamondburned/cchat/text"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// TokenAuthenticator is a first stage authenticator that allows the user to
|
||||
// authenticate directly using a token.
|
||||
type TokenAuthenticator struct{}
|
||||
|
||||
func NewTokenAuthenticator() cchat.Authenticator {
|
||||
return TokenAuthenticator{}
|
||||
}
|
||||
|
||||
func (TokenAuthenticator) Name() text.Rich {
|
||||
return text.Plain("Token")
|
||||
}
|
||||
|
||||
func (TokenAuthenticator) Description() text.Rich {
|
||||
return text.Plain("Log in using a token")
|
||||
}
|
||||
|
||||
func (TokenAuthenticator) AuthenticateForm() []cchat.AuthenticateEntry {
|
||||
return []cchat.AuthenticateEntry{
|
||||
{Name: "Token", Secret: true},
|
||||
}
|
||||
}
|
||||
|
||||
func (TokenAuthenticator) Authenticate(form []string) (cchat.Session, cchat.AuthenticateError) {
|
||||
if len(form) != 1 {
|
||||
return nil, cchat.WrapAuthenticateError(ErrMalformed)
|
||||
}
|
||||
|
||||
i, err := state.NewFromToken(form[0])
|
||||
if err != nil {
|
||||
return nil, cchat.WrapAuthenticateError(errors.Wrap(err, "failed to use token"))
|
||||
}
|
||||
|
||||
s, err := session.NewFromInstance(i)
|
||||
if err != nil {
|
||||
return nil, cchat.WrapAuthenticateError(errors.Wrap(err, "failed to make a session"))
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
77
internal/discord/authenticate/totp.go
Normal file
77
internal/discord/authenticate/totp.go
Normal file
|
@ -0,0 +1,77 @@
|
|||
package authenticate
|
||||
|
||||
import (
|
||||
"github.com/diamondburned/arikawa/api"
|
||||
"github.com/diamondburned/cchat"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/session"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/state"
|
||||
"github.com/diamondburned/cchat/text"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// ErrNeeds2FA is returned from Authenticator if the user login requires a 2FA
|
||||
// token.
|
||||
type ErrNeeds2FA struct {
|
||||
loginResp *api.LoginResponse
|
||||
}
|
||||
|
||||
func (err ErrNeeds2FA) Error() string {
|
||||
return "Two-Factor Authentication token required"
|
||||
}
|
||||
|
||||
func (err ErrNeeds2FA) NextStage() []cchat.Authenticator {
|
||||
return []cchat.Authenticator{
|
||||
NewTOTPAuthenticator(err.loginResp.Ticket),
|
||||
}
|
||||
}
|
||||
|
||||
// TOTPAuthenticator is a second stage authenticator that follows the normal
|
||||
// Authenticator if the user has Two-Factor Authentication enabled.
|
||||
type TOTPAuthenticator struct {
|
||||
client *api.Client
|
||||
ticket string
|
||||
}
|
||||
|
||||
func NewTOTPAuthenticator(ticket string) cchat.Authenticator {
|
||||
return &TOTPAuthenticator{
|
||||
client: api.NewClient(""),
|
||||
ticket: ticket,
|
||||
}
|
||||
}
|
||||
|
||||
func (auth *TOTPAuthenticator) Name() text.Rich {
|
||||
return text.Plain("2FA Prompt")
|
||||
}
|
||||
|
||||
func (auth *TOTPAuthenticator) Description() text.Rich {
|
||||
return text.Plain("Enter your 2FA token.")
|
||||
}
|
||||
|
||||
func (auth *TOTPAuthenticator) AuthenticateForm() []cchat.AuthenticateEntry {
|
||||
return []cchat.AuthenticateEntry{
|
||||
{Name: "Token", Description: "6-digit code"},
|
||||
}
|
||||
}
|
||||
|
||||
func (auth *TOTPAuthenticator) Authenticate(v []string) (cchat.Session, cchat.AuthenticateError) {
|
||||
if len(v) != 1 {
|
||||
return nil, cchat.WrapAuthenticateError(ErrMalformed)
|
||||
}
|
||||
|
||||
l, err := auth.client.TOTP(v[0], auth.ticket)
|
||||
if err != nil {
|
||||
return nil, cchat.WrapAuthenticateError(errors.Wrap(err, "failed to login with 2FA"))
|
||||
}
|
||||
|
||||
i, err := state.NewFromToken(l.Token)
|
||||
if err != nil {
|
||||
return nil, cchat.WrapAuthenticateError(errors.Wrap(err, "failed to use token"))
|
||||
}
|
||||
|
||||
s, err := session.NewFromInstance(i)
|
||||
if err != nil {
|
||||
return nil, cchat.WrapAuthenticateError(errors.Wrap(err, "failed to make a session"))
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
Loading…
Reference in a new issue