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 {
|
func (Service) Authenticate() []cchat.Authenticator {
|
||||||
return []cchat.Authenticator{
|
return authenticate.FirstStageAuthenticators()
|
||||||
authenticate.New(),
|
|
||||||
authenticate.NewDiscordLogin(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (Service) AsIconer() cchat.Iconer {
|
func (Service) AsIconer() cchat.Iconer {
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -4,7 +4,7 @@ go 1.14
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/diamondburned/arikawa v1.3.6
|
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/diamondburned/ningen v0.2.1-0.20201023061015-ce64ffb0bb12
|
||||||
github.com/dustin/go-humanize v1.0.0
|
github.com/dustin/go-humanize v1.0.0
|
||||||
github.com/go-test/deep v1.0.7
|
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.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 h1:vgFe8giVfwsAO+WpTYsTDIXvRUN48osVPNu0pZNvPEk=
|
||||||
github.com/diamondburned/cchat v0.3.8/go.mod h1:IlMtF+XIvAJh0GL/2yFdf0/34w+Hdy5A1GgvSwAXtQI=
|
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 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.20200621014632-6babb812b249/go.mod h1:xW9hpBZsGi8KpAh10TyP+YQlYBo+Xc+2w4TR6N0951A=
|
||||||
github.com/diamondburned/ningen v0.1.1-0.20200708085949-b64e350f3b8c h1:3h/kyk6HplYZF3zLi106itjYJWjbuMK/twijeGLEy2M=
|
github.com/diamondburned/ningen v0.1.1-0.20200708085949-b64e350f3b8c h1:3h/kyk6HplYZF3zLi106itjYJWjbuMK/twijeGLEy2M=
|
||||||
|
|
|
@ -4,8 +4,6 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"github.com/diamondburned/cchat"
|
"github.com/diamondburned/cchat"
|
||||||
"github.com/diamondburned/cchat-discord/internal/discord/session"
|
|
||||||
"github.com/diamondburned/cchat-discord/internal/discord/state"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -13,105 +11,12 @@ var (
|
||||||
EnterPassword = errors.New("enter your password")
|
EnterPassword = errors.New("enter your password")
|
||||||
)
|
)
|
||||||
|
|
||||||
type Authenticator struct {
|
// FirstStageAuthenticators constructs a slice of newly made first stage
|
||||||
username string
|
// authenticators.
|
||||||
password string
|
func FirstStageAuthenticators() []cchat.Authenticator {
|
||||||
}
|
return []cchat.Authenticator{
|
||||||
|
NewTokenAuthenticator(),
|
||||||
func New() cchat.Authenticator {
|
NewLoginAuthenticator(),
|
||||||
return &Authenticator{}
|
NewDiscordLogin(),
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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"
|
||||||
"github.com/diamondburned/cchat-discord/internal/discord/session"
|
"github.com/diamondburned/cchat-discord/internal/discord/session"
|
||||||
"github.com/diamondburned/cchat-discord/internal/discord/state"
|
"github.com/diamondburned/cchat-discord/internal/discord/state"
|
||||||
|
"github.com/diamondburned/cchat/text"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/skratchdot/open-golang/open"
|
"github.com/skratchdot/open-golang/open"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrDLNotFound = errors.New("DiscordLogin not found. Please install it from the GitHub page.")
|
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{}
|
type DiscordLoginAuth struct{}
|
||||||
|
|
||||||
func NewDiscordLogin() cchat.Authenticator {
|
func NewDiscordLogin() cchat.Authenticator {
|
||||||
return DiscordLoginAuth{}
|
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.
|
// AuthenticateForm returns an empty slice.
|
||||||
func (DiscordLoginAuth) AuthenticateForm() []cchat.AuthenticateEntry {
|
func (DiscordLoginAuth) AuthenticateForm() []cchat.AuthenticateEntry {
|
||||||
return []cchat.AuthenticateEntry{}
|
return []cchat.AuthenticateEntry{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Authenticate pops up discordlogin.
|
// Authenticate pops up DiscordLogin.
|
||||||
func (DiscordLoginAuth) Authenticate([]string) (cchat.Session, error) {
|
func (DiscordLoginAuth) Authenticate([]string) (cchat.Session, cchat.AuthenticateError) {
|
||||||
path, err := lookPathExtras("discordlogin")
|
path, err := lookPathExtras("discordlogin")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
openDiscordLoginPage()
|
openDiscordLoginPage()
|
||||||
return nil, ErrDLNotFound
|
return nil, cchat.WrapAuthenticateError(ErrDLNotFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := &exec.Cmd{Path: path}
|
cmd := &exec.Cmd{Path: path}
|
||||||
|
@ -40,19 +52,26 @@ func (DiscordLoginAuth) Authenticate([]string) (cchat.Session, error) {
|
||||||
|
|
||||||
b, err := cmd.Output()
|
b, err := cmd.Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "DiscordLogin failed")
|
return nil, cchat.WrapAuthenticateError(errors.Wrap(err, "DiscordLogin failed"))
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(b) == 0 {
|
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))
|
i, err := state.NewFromToken(string(b))
|
||||||
if err != nil {
|
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() {
|
func openDiscordLoginPage() {
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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 New Issue