Skip to content

Commit

Permalink
feat: add token caching (#20)
Browse files Browse the repository at this point in the history
Add option to cache MyQ auth token between container restarts (and
recreates if location is set to persistent mounted volume).
  • Loading branch information
brchri authored Oct 8, 2023
1 parent c1456ba commit 490eef2
Show file tree
Hide file tree
Showing 8 changed files with 138 additions and 14 deletions.
13 changes: 8 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,13 @@ This app uses the MQTT broker bundled with [TeslaMate](https://github.com/adrian
This app is provided as a docker image. You will need to download the [config.example.yml](config.example.yml) file (or the simplified [config.simple.example.yml](config.simple.example.yml)), edit it appropriately, and then mount it to the container at runtime. For example:

```bash
# see docker compose example below for parameter explanations
docker run \
-e MYQ_EMAIL=my_email@address.com \ # optional, can also be saved in the config.yml file
-e MYQ_PASS=my_super_secret_pass \ # optional, can also be saved in the config.yml file
-e TZ=America/New_York \ # optional, sets timezone for container
-v /etc/tesla-youq:/app/config:ro \ # required, mounts folder containing config file(s) into container
--user 1000:1000 \
-e MYQ_EMAIL=my_email@address.com \
-e MYQ_PASS=my_super_secret_pass \
-e TZ=America/New_York \
-v /etc/tesla-youq:/app/config \
brchri/tesla-youq:latest
```

Expand All @@ -43,12 +45,13 @@ services:
tesla-youq:
image: brchri/tesla-youq:latest
container_name: tesla-youq
user: 1000:1000 # optional, sets user to run in container; must have read access to mounted config volume (+ write if using token caching)
environment:
- MYQ_EMAIL=my_email@address.com # optional, can also be saved in the config.yml file
- MYQ_PASS=my_super_secret_pass # optional, can also be saved in the config.yml file
- TZ=America/New_York # optional, sets timezone for container
volumes:
- /etc/tesla-youq:/app/config:ro # required, mounts folder containing config file(s) into container
- /etc/tesla-youq:/app/config # required, mounts folder containing config file(s) into container
restart: unless-stopped
```
Expand Down
2 changes: 2 additions & 0 deletions config.example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ global:
cooldown: 5 # minutes to wait after operating garage before allowing another garage operation
myq_email: myq@example.com # email to auth to myq account; can also be passed as env var MYQ_EMAIL
myq_pass: super_secret_password # password to auth to myq account; can also be passed as env var MYQ_PASS
cache_token_file: config/token_cache.txt # location to cache myq auth token; omit to disable caching token; useful to prevent generating too many myq auth requests, especially when testing
# WARNING: using cache_token_file will store your auth token in plaintext at the specified location!

garage_doors:
- # main garage example
Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ module github.com/brchri/tesla-youq
go 1.19

require (
github.com/brchri/myq v0.0.0-20231002041725-18aa0c937db3
github.com/eclipse/paho.mqtt.golang v1.4.2
github.com/google/uuid v1.3.0
github.com/joeshaw/myq v0.0.0-20221122173250-4d1216b9fc87
github.com/stretchr/testify v1.8.4
gopkg.in/yaml.v3 v3.0.1
)
Expand All @@ -15,6 +15,6 @@ require (
github.com/gorilla/websocket v1.4.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/stretchr/objx v0.5.0 // indirect
golang.org/x/net v0.7.0 // indirect
golang.org/x/net v0.15.0 // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
)
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
github.com/brchri/myq v0.0.0-20231002041725-18aa0c937db3 h1:t5edNn/KiV1Kgz1Ll1kdpupABPUtOUDIebHLm5JmDV8=
github.com/brchri/myq v0.0.0-20231002041725-18aa0c937db3/go.mod h1:EDuAgiwrpS8cfzKCUrXpelEw1YOjxO/jhklEthAKmEs=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand All @@ -7,8 +9,6 @@ github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/joeshaw/myq v0.0.0-20221122173250-4d1216b9fc87 h1:8VEyynqlAGlKh7cd6VFUTXwbYPXsoQKWu0FGUisvE4o=
github.com/joeshaw/myq v0.0.0-20221122173250-4d1216b9fc87/go.mod h1:thEiR7+j6aW9O5xvqIp5njxp1UYjMwcg4P7+2XjeTJI=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
Expand All @@ -22,8 +22,8 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20210917221730-978cfadd31cf/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
Expand Down
47 changes: 45 additions & 2 deletions internal/geo/geo.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ package geo

import (
"fmt"
"io"
"log"
"math"
"os"
"time"

util "github.com/brchri/tesla-youq/internal/util"

"github.com/joeshaw/myq"
"github.com/brchri/myq"
)

// interface that allows api calls to myq to be abstracted and mocked by testing functions
Expand All @@ -19,6 +20,8 @@ type MyqSessionInterface interface {
SetDoorState(serialNumber, action string) error
SetUsername(string)
SetPassword(string)
GetToken() string
SetToken(string)
New()
}

Expand All @@ -40,7 +43,22 @@ func (m *MyqSessionWrapper) DeviceState(s string) (string, error) {
}

func (m *MyqSessionWrapper) Login() error {
return m.myqSession.Login()
err := m.myqSession.Login()
// cache token if requested
if err == nil && util.Config.Global.CacheTokenFile != "" {
file, fileErr := os.OpenFile(util.Config.Global.CacheTokenFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
if fileErr != nil {
log.Printf("WARNING: Unable to write to cache file %s", util.Config.Global.CacheTokenFile)
} else {
defer file.Close()

_, writeErr := file.WriteString(m.GetToken())
if writeErr != nil {
log.Printf("WARNING: Unable to write to cache file %s", util.Config.Global.CacheTokenFile)
}
}
}
return err
}

func (m *MyqSessionWrapper) SetDoorState(serialNumber, action string) error {
Expand All @@ -51,6 +69,14 @@ func (m *MyqSessionWrapper) New() {
m.myqSession = &myq.Session{}
}

func (m *MyqSessionWrapper) GetToken() string {
return m.myqSession.GetToken()
}

func (m *MyqSessionWrapper) SetToken(token string) {
m.myqSession.SetToken(token)
}

var myqExec MyqSessionInterface // executes myq package commands

func init() {
Expand Down Expand Up @@ -204,6 +230,23 @@ func setGarageDoor(config util.ConfigStruct, deviceSerial string, action string)
return nil
}

// check for cached token if we haven't retrieved it already
if util.Config.Global.CacheTokenFile != "" && myqExec.GetToken() == "" {
file, err := os.Open(util.Config.Global.CacheTokenFile)
if err != nil {
log.Printf("WARNING: Unable to read token cache from %s", util.Config.Global.CacheTokenFile)
} else {
defer file.Close()

data, err := io.ReadAll(file)
if err != nil {
log.Printf("WARNING: Unable to read token cache from %s", util.Config.Global.CacheTokenFile)
} else {
myqExec.SetToken(string(data))
}
}
}

curState, err := myqExec.DeviceState(deviceSerial)
if err != nil {
// fetching device state may have failed due to invalid session token; try fresh login to resolve
Expand Down
3 changes: 2 additions & 1 deletion internal/geo/geo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import (
"testing"
"time"

"github.com/brchri/myq"
"github.com/brchri/tesla-youq/internal/mocks"
"github.com/joeshaw/myq"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"

Expand All @@ -27,6 +27,7 @@ var (

func init() {
util.LoadConfig(filepath.Join("..", "..", "config.example.yml"))
util.Config.Global.CacheTokenFile = "" // dont assume cached token in testing

// used for testing events based on distance
distanceGarageDoor = util.Config.GarageDoors[0]
Expand Down
74 changes: 74 additions & 0 deletions internal/mocks/MyqSessionInterface.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions internal/util/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ type (
OpCooldown int `yaml:"cooldown"`
MyQEmail string `yaml:"myq_email"`
MyQPass string `yaml:"myq_pass"`
CacheTokenFile string `yaml:"cache_token_file"`
} `yaml:"global"`
GarageDoors []*GarageDoor `yaml:"garage_doors"`
Testing bool
Expand Down

0 comments on commit 490eef2

Please sign in to comment.