changed Authenticator impl to the new cchat API

This commit is contained in:
diamondburned 2020-10-27 14:35:22 -07:00
parent 7ce513cf68
commit 31c378db5a
8 changed files with 228 additions and 114 deletions

View File

@ -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
View File

@ -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
View File

@ -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=

View File

@ -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
}

View File

@ -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() {

View 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
}

View 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
}

View 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
}