Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ Based on the [mautrix-twilio](https://github.com/mautrix/twilio) bridge
mv config.yaml data/
```

5. Change `public_address` in `data/config.yaml` to your desired public address
5. Change `public_address` in `data/config.yaml` to your desired public address (default: `https://localhost:4000`).

6. Build and run the bridge using Docker (use -d for detached mode):

Expand All @@ -72,5 +72,16 @@ Based on the [mautrix-twilio](https://github.com/mautrix/twilio) bridge

## Login

### Using the LINE SelfHosted Bridge Bot

1. Open the Matrix client of your choice and start a chat with `@sh-linebot:your.matrix.homeserver.domain`. (For local beeper bridges, use `@sh-linebot:beeper.local`)
2. Send the command `login` and follow the instructions to log in to your LINE account.

or

### Via Beeper Desktop Settings

1. Open Beeper Desktop Settings
2. Navigate to `Bridges`
3. Click on the three dots next to your LINE Bridge and select `Experimental: Add an account`
4. Follow the instructions to log in to your LINE account.
89 changes: 49 additions & 40 deletions pkg/connector/connector.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,10 +122,11 @@ func (lc *LineConnector) CreateLogin(ctx context.Context, user *bridgev2.User, f
}

type LineEmailLogin struct {
User *bridgev2.User
Email string
Password string
Verifier string
User *bridgev2.User
Email string
Password string
Verifier string
AwaitingPIN bool

pollResult chan *line.LoginResult
pollErr chan error
Expand All @@ -134,6 +135,7 @@ type LineEmailLogin struct {
}

var _ bridgev2.LoginProcessUserInput = (*LineEmailLogin)(nil)
var _ bridgev2.LoginProcessDisplayAndWait = (*LineEmailLogin)(nil)

func (ll *LineEmailLogin) Start(ctx context.Context) (*bridgev2.LoginStep, error) {
return &bridgev2.LoginStep{
Expand All @@ -159,18 +161,7 @@ func (ll *LineEmailLogin) Start(ctx context.Context) (*bridgev2.LoginStep, error

func (ll *LineEmailLogin) SubmitUserInput(ctx context.Context, input map[string]string) (*bridgev2.LoginStep, error) {
if ll.Verifier != "" {
// User clicked "Continue" on the PIN screen. Wait for the background polling to finish.
select {
case res := <-ll.pollResult:
if res.AuthToken != "" {
return ll.finishLogin(ctx, res)
}
case err := <-ll.pollErr:
return nil, fmt.Errorf("verification failed: %w", err)
case <-ctx.Done():
return nil, ctx.Err()
}
return nil, fmt.Errorf("unexpected end of polling")
return ll.Wait(ctx)
}

if input["email"] != "" {
Expand All @@ -188,21 +179,52 @@ func (ll *LineEmailLogin) SubmitUserInput(ctx context.Context, input map[string]
return nil, fmt.Errorf("login failed: %w", err)
}

return ll.handleLoginResponse(ctx, res)
}

func (ll *LineEmailLogin) Wait(ctx context.Context) (*bridgev2.LoginStep, error) {
if ll.Verifier != "" {
select {
case res := <-ll.pollResult:
if res.AuthToken != "" {
return ll.finishLogin(ctx, res)
}
Comment thread
highesttt marked this conversation as resolved.
return nil, fmt.Errorf("verification failed: no auth token received")
case err := <-ll.pollErr:
return nil, fmt.Errorf("verification failed: %w", err)
case <-ctx.Done():
return nil, ctx.Err()
}
}

if ll.AwaitingPIN {
client := line.NewClient("")
res, err := client.Login(ll.Email, ll.Password)
if err != nil {
return nil, fmt.Errorf("login failed: %w", err)
}
return ll.handleLoginResponse(ctx, res)
}

return nil, fmt.Errorf("no pending login continuation")
}

func (ll *LineEmailLogin) handleLoginResponse(ctx context.Context, res *line.LoginResult) (*bridgev2.LoginStep, error) {
if res.AuthToken != "" {
return ll.finishLogin(ctx, res)
}

if (res.Type == 3 || res.Type == 0) && res.Verifier != "" {
ll.Verifier = res.Verifier
instructions := "A verification request has been sent to your LINE device."
ll.AwaitingPIN = false
instructions := "Please open the LINE app on your mobile device to complete the login."
pin := res.Pin
if res.PinCode != "" {
pin = res.PinCode
}
if pin != "" {
instructions += fmt.Sprintf("\n\n**Please enter this PIN code on your mobile device: %s**", pin)
instructions = fmt.Sprintf("Open LINE and enter this PIN on your mobile device: %s", pin)
}
instructions += "\n\nAfter entering the code (or approving), click 'Continue' to finish login."

// Start polling in background immediately so it's running while the user enters the PIN
ll.mu.Lock()
Expand All @@ -221,36 +243,23 @@ func (ll *LineEmailLogin) SubmitUserInput(ctx context.Context, input map[string]
ll.mu.Unlock()

return &bridgev2.LoginStep{
Type: bridgev2.LoginStepTypeUserInput,
Type: bridgev2.LoginStepTypeDisplayAndWait,
StepID: "dev.highest.matrix.line.wait_verification",
Instructions: instructions,
UserInputParams: &bridgev2.LoginUserInputParams{
Fields: []bridgev2.LoginInputDataField{
{
Type: bridgev2.LoginInputFieldTypePassword, // hidden field just to have a submit button
ID: "dummy",
Name: "Action",
Description: "Press Enter/Continue when done",
},
},
DisplayAndWaitParams: &bridgev2.LoginDisplayAndWaitParams{
Type: bridgev2.LoginDisplayTypeNothing,
},
}, nil
}

if res.Certificate != "" {
ll.AwaitingPIN = true
return &bridgev2.LoginStep{
Type: bridgev2.LoginStepTypeUserInput,
Type: bridgev2.LoginStepTypeDisplayAndWait,
StepID: "dev.highest.matrix.line.enter_pin",
Instructions: fmt.Sprintf("Please open the LINE app on your mobile device and enter this PIN code: **%s**\n\nAfter entering the code, click 'Continue' below.", res.Certificate),
UserInputParams: &bridgev2.LoginUserInputParams{
Fields: []bridgev2.LoginInputDataField{
{
Type: bridgev2.LoginInputFieldTypePassword, // hidden dummy field
ID: "dummy",
Name: "Hidden",
Description: "Press Enter to continue",
},
},
Instructions: fmt.Sprintf("Please open the LINE app on your mobile device and enter this PIN code: **%s**\n\nAfter entering the code, click Continue below.", res.Certificate),
DisplayAndWaitParams: &bridgev2.LoginDisplayAndWaitParams{
Type: bridgev2.LoginDisplayTypeNothing,
},
}, nil
}
Expand Down