Restructure device tree and remove soc server (#1060)
This commit is contained in:
parent
70da155c3d
commit
80177aa0ad
160 changed files with 60 additions and 1450 deletions
11
Makefile
11
Makefile
|
|
@ -102,13 +102,4 @@ image-update:
|
|||
|
||||
soc:
|
||||
@echo Version: $(VERSION) $(BUILD_DATE)
|
||||
go build -o evcc-soc $(BUILD_TAGS) $(BUILD_ARGS) github.com/andig/evcc/soc/client
|
||||
|
||||
server:
|
||||
@echo Version: $(VERSION) $(BUILD_DATE)
|
||||
go build -o soc-server $(BUILD_TAGS) $(BUILD_ARGS) github.com/andig/evcc/soc/server
|
||||
|
||||
publish-server:
|
||||
GOOS=linux GOARCH=amd64 go build -o soc-server $(BUILD_TAGS) $(BUILD_ARGS) github.com/andig/evcc/soc/server
|
||||
docker build -f soc/Dockerfile --platform linux/amd64 -t andig/evcc-cloud .
|
||||
docker push andig/evcc-cloud
|
||||
go build -o evcc-soc $(BUILD_TAGS) $(BUILD_ARGS) github.com/andig/evcc/cmd/soc
|
||||
|
|
|
|||
4
api/plugin.go
Normal file
4
api/plugin.go
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
package api
|
||||
|
||||
// Custom meter/charger/vehicle type
|
||||
const Custom = "custom"
|
||||
|
|
@ -4,7 +4,6 @@ import (
|
|||
"fmt"
|
||||
|
||||
"github.com/andig/evcc/api"
|
||||
"github.com/andig/evcc/internal"
|
||||
"github.com/andig/evcc/provider"
|
||||
"github.com/andig/evcc/util"
|
||||
)
|
||||
|
|
@ -19,7 +18,7 @@ type Charger struct {
|
|||
|
||||
func init() {
|
||||
registry.Add("default", NewConfigurableFromConfig)
|
||||
registry.Add(internal.Custom, NewConfigurableFromConfig)
|
||||
registry.Add(api.Custom, NewConfigurableFromConfig)
|
||||
}
|
||||
|
||||
// NewConfigurableFromConfig creates a new configurable charger
|
||||
|
|
@ -7,8 +7,8 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/andig/evcc/api"
|
||||
"github.com/andig/evcc/charger/easee"
|
||||
"github.com/andig/evcc/core"
|
||||
"github.com/andig/evcc/internal/charger/easee"
|
||||
"github.com/andig/evcc/util"
|
||||
"github.com/andig/evcc/util/request"
|
||||
"github.com/andig/evcc/util/sponsor"
|
||||
|
|
@ -58,7 +58,7 @@ func init() {
|
|||
registry.Add("evsewifi", NewEVSEWifiFromConfig)
|
||||
}
|
||||
|
||||
//go:generate go run ../../cmd/tools/decorate.go -f decorateEVSE -b *EVSEWifi -r api.Charger -t "api.Meter,CurrentPower,func() (float64, error)" -t "api.MeterEnergy,TotalEnergy,func() (float64, error)" -t "api.MeterCurrent,Currents,func() (float64, float64, float64, error)"
|
||||
//go:generate go run ../cmd/tools/decorate.go -f decorateEVSE -b *EVSEWifi -r api.Charger -t "api.Meter,CurrentPower,func() (float64, error)" -t "api.MeterEnergy,TotalEnergy,func() (float64, error)" -t "api.MeterCurrent,Currents,func() (float64, float64, float64, error)"
|
||||
|
||||
// NewEVSEWifiFromConfig creates a EVSEWifi charger from generic config
|
||||
func NewEVSEWifiFromConfig(other map[string]interface{}) (api.Charger, error) {
|
||||
|
|
@ -12,7 +12,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/andig/evcc/api"
|
||||
"github.com/andig/evcc/internal/charger/fritzdect"
|
||||
"github.com/andig/evcc/charger/fritzdect"
|
||||
"github.com/andig/evcc/util"
|
||||
"github.com/andig/evcc/util/request"
|
||||
"golang.org/x/text/encoding/unicode"
|
||||
|
|
@ -8,7 +8,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/andig/evcc/api"
|
||||
"github.com/andig/evcc/internal/charger/keba"
|
||||
"github.com/andig/evcc/charger/keba"
|
||||
"github.com/andig/evcc/util"
|
||||
)
|
||||
|
||||
|
|
@ -8,7 +8,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/andig/evcc/api"
|
||||
"github.com/andig/evcc/internal/charger/nrgble"
|
||||
"github.com/andig/evcc/charger/nrgble"
|
||||
"github.com/andig/evcc/util"
|
||||
"github.com/godbus/dbus/v5"
|
||||
"github.com/lunixbochs/struc"
|
||||
|
|
@ -5,7 +5,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/andig/evcc/api"
|
||||
"github.com/andig/evcc/internal/charger/openwb"
|
||||
"github.com/andig/evcc/charger/openwb"
|
||||
"github.com/andig/evcc/provider"
|
||||
"github.com/andig/evcc/provider/mqtt"
|
||||
"github.com/andig/evcc/util"
|
||||
|
|
@ -32,7 +32,7 @@ func init() {
|
|||
registry.Add("phoenix-em-eth", NewPhoenixEMEthFromConfig)
|
||||
}
|
||||
|
||||
//go:generate go run ../../cmd/tools/decorate.go -f decoratePhoenixEMEth -b *PhoenixEMEth -r api.Charger -t "api.Meter,CurrentPower,func() (float64, error)" -t "api.MeterEnergy,TotalEnergy,func() (float64, error)" -t "api.MeterCurrent,Currents,func() (float64, float64, float64, error)"
|
||||
//go:generate go run ../cmd/tools/decorate.go -f decoratePhoenixEMEth -b *PhoenixEMEth -r api.Charger -t "api.Meter,CurrentPower,func() (float64, error)" -t "api.MeterEnergy,TotalEnergy,func() (float64, error)" -t "api.MeterCurrent,Currents,func() (float64, float64, float64, error)"
|
||||
|
||||
// NewPhoenixEMEthFromConfig creates a Phoenix charger from generic config
|
||||
func NewPhoenixEMEthFromConfig(other map[string]interface{}) (api.Charger, error) {
|
||||
|
|
@ -33,7 +33,7 @@ func init() {
|
|||
registry.Add("phoenix-ev-eth", NewPhoenixEVEthFromConfig)
|
||||
}
|
||||
|
||||
//go:generate go run ../../cmd/tools/decorate.go -f decoratePhoenixEVEth -b *PhoenixEVEth -r api.Charger -t "api.Meter,CurrentPower,func() (float64, error)" -t "api.MeterEnergy,TotalEnergy,func() (float64, error)" -t "api.MeterCurrent,Currents,func() (float64, float64, float64, error)"
|
||||
//go:generate go run ../cmd/tools/decorate.go -f decoratePhoenixEVEth -b *PhoenixEVEth -r api.Charger -t "api.Meter,CurrentPower,func() (float64, error)" -t "api.MeterEnergy,TotalEnergy,func() (float64, error)" -t "api.MeterCurrent,Currents,func() (float64, float64, float64, error)"
|
||||
|
||||
// NewPhoenixEVEthFromConfig creates a Phoenix charger from generic config
|
||||
func NewPhoenixEVEthFromConfig(other map[string]interface{}) (api.Charger, error) {
|
||||
|
|
@ -7,7 +7,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/andig/evcc/api"
|
||||
"github.com/andig/evcc/internal/charger/tasmota"
|
||||
"github.com/andig/evcc/charger/tasmota"
|
||||
"github.com/andig/evcc/util"
|
||||
"github.com/andig/evcc/util/request"
|
||||
)
|
||||
|
|
@ -11,7 +11,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/andig/evcc/api"
|
||||
"github.com/andig/evcc/internal/charger/tplink"
|
||||
"github.com/andig/evcc/charger/tplink"
|
||||
"github.com/andig/evcc/util"
|
||||
)
|
||||
|
||||
|
|
@ -64,7 +64,7 @@ type Warp struct {
|
|||
enabled bool // cache
|
||||
}
|
||||
|
||||
//go:generate go run ../../cmd/tools/decorate.go -f decorateWarp -b *Warp -r api.Charger -t "api.Meter,CurrentPower,func() (float64, error)" -t "api.MeterEnergy,TotalEnergy,func() (float64, error)"
|
||||
//go:generate go run ../cmd/tools/decorate.go -f decorateWarp -b *Warp -r api.Charger -t "api.Meter,CurrentPower,func() (float64, error)" -t "api.MeterEnergy,TotalEnergy,func() (float64, error)"
|
||||
|
||||
// NewWarp creates a new configurable charger
|
||||
func NewWarp(mqttconf mqtt.Config, topic string, timeout time.Duration) (*Warp, error) {
|
||||
|
|
@ -5,12 +5,12 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/andig/evcc/api"
|
||||
"github.com/andig/evcc/internal/charger"
|
||||
"github.com/andig/evcc/internal/meter"
|
||||
"github.com/andig/evcc/internal/vehicle"
|
||||
"github.com/andig/evcc/charger"
|
||||
"github.com/andig/evcc/meter"
|
||||
"github.com/andig/evcc/provider/mqtt"
|
||||
"github.com/andig/evcc/push"
|
||||
"github.com/andig/evcc/server"
|
||||
"github.com/andig/evcc/vehicle"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
|
|
|
|||
|
|
@ -8,13 +8,13 @@ import (
|
|||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/andig/evcc/api/proto/pb"
|
||||
"github.com/andig/evcc/core"
|
||||
"github.com/andig/evcc/hems"
|
||||
"github.com/andig/evcc/provider/javascript"
|
||||
"github.com/andig/evcc/provider/mqtt"
|
||||
"github.com/andig/evcc/push"
|
||||
"github.com/andig/evcc/server"
|
||||
"github.com/andig/evcc/soc/proto/pb"
|
||||
"github.com/andig/evcc/util"
|
||||
"github.com/andig/evcc/util/cloud"
|
||||
"github.com/andig/evcc/util/pipe"
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/andig/evcc/api"
|
||||
"github.com/andig/evcc/internal/vehicle"
|
||||
"github.com/andig/evcc/util"
|
||||
"github.com/andig/evcc/vehicle"
|
||||
)
|
||||
|
||||
func usage() {
|
||||
|
|
@ -4,7 +4,7 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/andig/evcc/internal/charger/keba"
|
||||
"github.com/andig/evcc/charger/keba"
|
||||
"github.com/andig/evcc/util"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/andig/evcc/internal/meter/sma"
|
||||
"github.com/andig/evcc/meter/sma"
|
||||
"github.com/andig/evcc/util"
|
||||
)
|
||||
|
||||
|
|
|
|||
5
go.mod
5
go.mod
|
|
@ -14,7 +14,6 @@ require (
|
|||
github.com/containrrr/shoutrrr v0.4.4
|
||||
github.com/deepmap/oapi-codegen v1.6.1 // indirect
|
||||
github.com/denisbrodbeck/machineid v1.0.1
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||
github.com/dylanmei/iso8601 v0.1.0
|
||||
github.com/eclipse/paho.mqtt.golang v1.3.4
|
||||
github.com/fatih/structs v1.1.0
|
||||
|
|
@ -61,8 +60,6 @@ require (
|
|||
github.com/prometheus/common v0.23.0 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/robertkrimen/otto v0.0.0-20200922221731-ef014fd054ac
|
||||
github.com/shurcooL/githubv4 v0.0.0-20201206200315-234843c633fa
|
||||
github.com/shurcooL/graphql v0.0.0-20200928012149-18c5c3165e3a // indirect
|
||||
github.com/sirupsen/logrus v1.8.1 // indirect
|
||||
github.com/spf13/afero v1.6.0 // indirect
|
||||
github.com/spf13/cobra v1.1.3
|
||||
|
|
@ -72,7 +69,7 @@ require (
|
|||
github.com/thoas/go-funk v0.8.0
|
||||
github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c
|
||||
github.com/volkszaehler/mbmd v0.0.0-20210117183837-59dcc46d62d4
|
||||
golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf
|
||||
golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf // indirect
|
||||
golang.org/x/mod v0.4.2 // indirect
|
||||
golang.org/x/net v0.0.0-20210508051633-16afe75a6701
|
||||
golang.org/x/oauth2 v0.0.0-20210427180440-81ed05c6b58c
|
||||
|
|
|
|||
5
go.sum
5
go.sum
|
|
@ -135,7 +135,6 @@ github.com/deepmap/oapi-codegen v1.6.1 h1:2BvsmRb6pogGNtr8Ann+esAbSKFXx2CZN18VpA
|
|||
github.com/deepmap/oapi-codegen v1.6.1/go.mod h1:ryDa9AgbELGeB+YEXE1dR53yAjHwFvE9iAUlWl9Al3M=
|
||||
github.com/denisbrodbeck/machineid v1.0.1 h1:geKr9qtkB876mXguW2X6TU4ZynleN6ezuMSRhl4D7AQ=
|
||||
github.com/denisbrodbeck/machineid v1.0.1/go.mod h1:dJUwb7PTidGDeYyUBmXZ2GphQBbjJCrnectwCyxcUSI=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
|
|
@ -630,10 +629,6 @@ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD
|
|||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/shurcooL/githubv4 v0.0.0-20201206200315-234843c633fa h1:jozR3igKlnYCj9IVHOVump59bp07oIRoLQ/CcjMYIUA=
|
||||
github.com/shurcooL/githubv4 v0.0.0-20201206200315-234843c633fa/go.mod h1:hAF0iLZy4td2EX+/8Tw+4nodhlMrwN3HupfaXj3zkGo=
|
||||
github.com/shurcooL/graphql v0.0.0-20200928012149-18c5c3165e3a h1:KikTa6HtAK8cS1qjvUvvq4QO21QnwC+EfvB+OAuZ/ZU=
|
||||
github.com/shurcooL/graphql v0.0.0-20200928012149-18c5c3165e3a/go.mod h1:AuYgA5Kyo4c7HfUmvRGs/6rGlMMV/6B1bVnB9JxJEEg=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.0.5/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
|
|
|
|||
|
|
@ -1,4 +0,0 @@
|
|||
package internal
|
||||
|
||||
// Custom plugin type
|
||||
const Custom = "custom"
|
||||
|
|
@ -5,17 +5,16 @@ import (
|
|||
"fmt"
|
||||
|
||||
"github.com/andig/evcc/api"
|
||||
"github.com/andig/evcc/internal"
|
||||
"github.com/andig/evcc/provider"
|
||||
"github.com/andig/evcc/util"
|
||||
)
|
||||
|
||||
func init() {
|
||||
registry.Add("default", NewConfigurableFromConfig)
|
||||
registry.Add(internal.Custom, NewConfigurableFromConfig)
|
||||
registry.Add(api.Custom, NewConfigurableFromConfig)
|
||||
}
|
||||
|
||||
//go:generate go run ../../cmd/tools/decorate.go -f decorateMeter -b api.Meter -t "api.MeterEnergy,TotalEnergy,func() (float64, error)" -t "api.MeterCurrent,Currents,func() (float64, float64, float64, error)" -t "api.Battery,SoC,func() (float64, error)"
|
||||
//go:generate go run ../cmd/tools/decorate.go -f decorateMeter -b api.Meter -t "api.MeterEnergy,TotalEnergy,func() (float64, error)" -t "api.MeterCurrent,Currents,func() (float64, float64, float64, error)" -t "api.Battery,SoC,func() (float64, error)"
|
||||
|
||||
// NewConfigurableFromConfig creates api.Meter from config
|
||||
func NewConfigurableFromConfig(other map[string]interface{}) (api.Meter, error) {
|
||||
|
|
@ -27,7 +27,7 @@ func init() {
|
|||
registry.Add("modbus", NewModbusFromConfig)
|
||||
}
|
||||
|
||||
//go:generate go run ../../cmd/tools/decorate.go -f decorateModbus -b api.Meter -t "api.MeterEnergy,TotalEnergy,func() (float64, error)" -t "api.Battery,SoC,func() (float64, error)"
|
||||
//go:generate go run ../cmd/tools/decorate.go -f decorateModbus -b api.Meter -t "api.MeterEnergy,TotalEnergy,func() (float64, error)" -t "api.Battery,SoC,func() (float64, error)"
|
||||
|
||||
// NewModbusFromConfig creates api.Meter from config
|
||||
func NewModbusFromConfig(other map[string]interface{}) (api.Meter, error) {
|
||||
|
|
@ -7,7 +7,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/andig/evcc/api"
|
||||
"github.com/andig/evcc/internal/charger/openwb"
|
||||
"github.com/andig/evcc/charger/openwb"
|
||||
"github.com/andig/evcc/provider"
|
||||
"github.com/andig/evcc/provider/mqtt"
|
||||
"github.com/andig/evcc/util"
|
||||
|
|
@ -6,7 +6,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/andig/evcc/api"
|
||||
"github.com/andig/evcc/internal/meter/sma"
|
||||
"github.com/andig/evcc/meter/sma"
|
||||
"github.com/andig/evcc/util"
|
||||
)
|
||||
|
||||
|
|
@ -37,7 +37,7 @@ func init() {
|
|||
registry.Add("sma", NewSMAFromConfig)
|
||||
}
|
||||
|
||||
//go:generate go run ../../cmd/tools/decorate.go -f decorateSMA -b api.Meter -t "api.MeterEnergy,TotalEnergy,func() (float64, error)"
|
||||
//go:generate go run ../cmd/tools/decorate.go -f decorateSMA -b api.Meter -t "api.MeterEnergy,TotalEnergy,func() (float64, error)"
|
||||
|
||||
// NewSMAFromConfig creates a SMA Meter from generic config
|
||||
func NewSMAFromConfig(other map[string]interface{}) (api.Meter, error) {
|
||||
|
|
@ -3,7 +3,7 @@ package meter
|
|||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/andig/evcc/internal/meter/sma"
|
||||
"github.com/andig/evcc/meter/sma"
|
||||
"github.com/andig/evcc/util"
|
||||
)
|
||||
|
||||
|
|
@ -9,7 +9,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/andig/evcc/api"
|
||||
"github.com/andig/evcc/internal/meter/powerwall"
|
||||
"github.com/andig/evcc/meter/powerwall"
|
||||
"github.com/andig/evcc/util"
|
||||
"github.com/andig/evcc/util/request"
|
||||
)
|
||||
|
|
@ -26,7 +26,7 @@ func init() {
|
|||
registry.Add("tesla", NewTeslaFromConfig)
|
||||
}
|
||||
|
||||
//go:generate go run ../../cmd/tools/decorate.go -f decorateTesla -b api.Meter -t "api.MeterEnergy,TotalEnergy,func() (float64, error)" -t "api.Battery,SoC,func() (float64, error)"
|
||||
//go:generate go run ../cmd/tools/decorate.go -f decorateTesla -b api.Meter -t "api.MeterEnergy,TotalEnergy,func() (float64, error)" -t "api.Battery,SoC,func() (float64, error)"
|
||||
|
||||
// NewTeslaFromConfig creates a Tesla Powerwall Meter from generic config
|
||||
func NewTeslaFromConfig(other map[string]interface{}) (api.Meter, error) {
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
# STEP 1 build executable binary
|
||||
# FROM golang:1.16-alpine as builder
|
||||
|
||||
# # Install git + SSL ca certificates.
|
||||
# # Git is required for fetching the dependencies.
|
||||
# # Ca-certificates is required to call HTTPS endpoints.
|
||||
# RUN apk update && apk add --no-cache git ca-certificates tzdata alpine-sdk && update-ca-certificates
|
||||
|
||||
# WORKDIR /build
|
||||
|
||||
# # install go tools and cache modules
|
||||
# COPY Makefile .
|
||||
# COPY go.mod .
|
||||
# COPY go.sum .
|
||||
# RUN make install
|
||||
# RUN go mod download
|
||||
|
||||
# # build
|
||||
# COPY . .
|
||||
# RUN make server
|
||||
|
||||
FROM alpine:3.13 as builder
|
||||
RUN apk update && apk add --no-cache git ca-certificates tzdata && update-ca-certificates
|
||||
|
||||
# STEP 2 build a small image including module support
|
||||
FROM alpine:3.13
|
||||
|
||||
ENV TZ=Europe/Berlin
|
||||
|
||||
# Import from builder
|
||||
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
|
||||
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
||||
COPY soc-server /usr/local/bin/soc-server
|
||||
|
||||
CMD [ "soc-server" ]
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIE1DCCArwCCQC/TbpAPhcOQTANBgkqhkiG9w0BAQsFADAsMQswCQYDVQQGEwJE
|
||||
RTEdMBsGCSqGSIb3DQEJARYOY3B1aWRsZUBnbXguZGUwHhcNMjEwMzMxMDc1ODAz
|
||||
WhcNMjIwMzMxMDc1ODAzWjAsMQswCQYDVQQGEwJERTEdMBsGCSqGSIb3DQEJARYO
|
||||
Y3B1aWRsZUBnbXguZGUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC+
|
||||
noDTXbidh/5RFfsLlMJbfMn4Vu91oJb4MM41RQM8UOr3SRU/ZzcoLfWJe2ePpEDp
|
||||
ZL4piobs8EIjFcWU2C/8La0rygbcDDqUJkW/nViH0Drs5ctgJl/uYeiDLnZiSZO+
|
||||
PBoOA0trXxqlJPxuEseJbFaLvVE81DsLBnltDPqhJZEUJPEUIjtfStOhQM0f5YoO
|
||||
Greewk7P5LUkTAuTIz+xk6uKnC1YF89SKmdPMH26Rcy6q1IqhEQG3tjUliJZiKS9
|
||||
eQlnp9gskqNGop9gEsmFIMnhlrTUsVU2q38zLDFcmNDbNkRvv4Tf+7jHo4tLs02m
|
||||
/fMTikdMNCRMOcJKbdobcwXq4Ghvc85xeM4/8wIQHEDFbLzmxi/4ibFfJjOfQOe9
|
||||
VpK+TbeSBTxBw42CH9V0TUOz/LELYkMrJ+zfwMCvTV4Eodmc0gjwiy9aIYdpZXKF
|
||||
ueqGGljhVCq2XqkaSATFu5CsdPZrfUszLiAVqpuVTMjy3VQ9ICzz1Bd5ICBRFt6l
|
||||
fDsvfB6SgmB9r1bC+yr/tqe/unzUPajL5mZwn7Jev0uOl6mBNmQpcIEne+9cKedf
|
||||
vPCv/tUQpwpz6blGacEtTxuM+fWDk+CLpPnId/4PEnIQWRFE2aXQ4nrJSwu3O5+k
|
||||
vuZNWety8MOSkuQr0toH//p3VNHOtT7L4JWSlDfTZQIDAQABMA0GCSqGSIb3DQEB
|
||||
CwUAA4ICAQANoLbh4Di1A485ggnKfp9ykfVvg+NduGm+eqr27be033N6IJ7fuKmF
|
||||
7Ki1E+sp0IJDMdyikbqoHdbroHu2chwp/1GzFxhx3Vo7kswS/ehSxWpHjhtrGjAF
|
||||
tSDUFR9q4C+km2A3k1ZNyd9C1w+R9Lr16j9lBQoGWmUgFsRf8ED16FMMSK/1mCC0
|
||||
1VSydYnYhKmPZxMByTozGTV97wihA0XqXtadbkQoyvcnEuarBvEX3mPA3effqcEG
|
||||
rreeDp1yzYtQRW85ZASaqgF5CYKhe9NekurZ5Jd+2mQPYRWPFpIFsSHoOLqbfcN1
|
||||
/S0Si6LZWq0Mi8cbSi/zq+Eh7Q0w+EXP6Goh0A7M4e1dKt+cfGQpeyeg3cF49Cda
|
||||
RrTkSv2Sl2jbrShqAsG/HNhyBaI/gtLGi9tig9wAoV2zjGw1Ehs0GcwFq2g/f6Zv
|
||||
W7rdZIBZT1RIQLczuwsEhv6cFrSM5OU2f5fuKnmeI6uvyz0jRqiMncYRjwI6LotB
|
||||
sWmdTWi6xsctYirJ3Yip5Tqm01asyUuIeiT+eQ6I9CfHLiGBXEzxlK5PrFqfD7gK
|
||||
LfB1gCN7SAlxKGepmFMwfF+fDrsurL2T2ePxaaNBLjRAzmpSpH0NedMd+eanuHXV
|
||||
a/iOL2XmVis7iuFyk5M2XtLFfYBff+mxSPA8d9u5kCpoel0tx3iLmw==
|
||||
-----END CERTIFICATE-----
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
package ca
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
_ "embed"
|
||||
)
|
||||
|
||||
//go:embed ca-cert.pem
|
||||
var cert []byte
|
||||
|
||||
func PEM() []byte {
|
||||
copy := bytes.NewBuffer(cert)
|
||||
return copy.Bytes()
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
set +ex
|
||||
|
||||
rm *.pem
|
||||
|
||||
# 1. Generate CA's private key and self-signed certificate
|
||||
openssl req -x509 -newkey rsa:4096 -days 365 -nodes -keyout ca-key.pem -out ca-cert.pem -subj "/C=DE/emailAddress=cpuidle@gmx.de"
|
||||
|
||||
echo "CA's self-signed certificate"
|
||||
openssl x509 -in ca-cert.pem -noout -text
|
||||
|
||||
# 2. Generate web server's private key and certificate signing request (CSR)
|
||||
openssl req -newkey rsa:4096 -nodes -keyout server-key.pem -out server-req.pem -subj "/C=DE/emailAddress=cpuidle@gmx.de"
|
||||
|
||||
# 3. Use CA's private key to sign web server's CSR and get back the signed certificate
|
||||
openssl x509 -req -in server-req.pem -days 60 -CA ca-cert.pem -CAkey ca-key.pem -CAcreateserial -out server-cert.pem -extfile server-ext.cnf
|
||||
|
||||
echo "Server's signed certificate"
|
||||
openssl x509 -in server-cert.pem -noout -text
|
||||
|
||||
# 4. Generate client's private key and certificate signing request (CSR)
|
||||
openssl req -newkey rsa:4096 -nodes -keyout client-key.pem -out client-req.pem -subj "/C=DE/emailAddress=cpuidle@gmx.de"
|
||||
|
||||
# 5. Use CA's private key to sign client's CSR and get back the signed certificate
|
||||
openssl x509 -req -in client-req.pem -days 60 -CA ca-cert.pem -CAkey ca-key.pem -CAcreateserial -out client-cert.pem -extfile client-ext.cnf
|
||||
|
||||
echo "Client's signed certificate"
|
||||
openssl x509 -in client-cert.pem -noout -text
|
||||
|
||||
rm *-req.pem
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
_ "embed"
|
||||
"encoding/base64"
|
||||
|
||||
"github.com/andig/evcc/util"
|
||||
)
|
||||
|
||||
//go:embed server-cert.pem
|
||||
var cert []byte
|
||||
|
||||
//--go:embed server-key.pem
|
||||
var key []byte
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
key, err = base64.StdEncoding.DecodeString(util.Getenv("SERVER_KEY"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func PEM() []byte {
|
||||
copy := bytes.NewBuffer(cert)
|
||||
return copy.Bytes()
|
||||
}
|
||||
|
||||
func Certificate() (tls.Certificate, error) {
|
||||
return tls.X509KeyPair(cert, key)
|
||||
}
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIE+TCCAuGgAwIBAgIJALu7hrWUWtC8MA0GCSqGSIb3DQEBBQUAMCwxCzAJBgNV
|
||||
BAYTAkRFMR0wGwYJKoZIhvcNAQkBFg5jcHVpZGxlQGdteC5kZTAeFw0yMTAzMzEw
|
||||
NzU4MDVaFw0yMTA1MzAwNzU4MDVaMCwxCzAJBgNVBAYTAkRFMR0wGwYJKoZIhvcN
|
||||
AQkBFg5jcHVpZGxlQGdteC5kZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC
|
||||
ggIBALzv8oy3yl+sH+7fezx6aDJWf77aCaZmBrQyupXmd8InJazRx8ScmpO+LJ7i
|
||||
ZtVA1YuHbL2qZaV5SBk15UkRaVRRNqVitQmaRFYDrrAtDMosOhVNGOSorGnrlOqP
|
||||
eu8LD/bWwZ57NqWE2z7ZTnqNNdgX1urcvzs7jmopKl4i71sKHAJ1jdzEYl+7l7r1
|
||||
HdtKU4K6axQ2BBP6dHB9v0wOJQzfER76mSWe+hotCTdIiGnZhaIO5NoW31i3KkPi
|
||||
277bLse5wGGbD1FvzLPaWf0HyruxARPs49kxuxnYtJ+KUDk5LXpDC6MM2QcUdV/8
|
||||
HVkTadONW/gVFKWS7u21NrWH2342K0lz4FyNEwiQGmTCnyT4A9DQmdfWirnXsf4M
|
||||
Jkag7fWqK/dEFIJK14RHZx+i53UBIoLu6mTT+MrHzdXRNewg9jlSwOYROG80DEMh
|
||||
WIbTcYqUk9XPSUzzaeg2FuP7ZFEmpbsltsztaEQikd5PO9Dkl+Qzm5eiQMwNxJX5
|
||||
bgb6eLQOI+HOtQHSWVTBXeM7Vf0JCfA7RAvKgRk4lK6JGijzGBSLuCLzXAPygiqb
|
||||
59KgYf2EDaSmjzU+ZCLDR6l81SUDVnQj75obQt/TPBoVgwlCeOfFvLGGXY6tzJrp
|
||||
8yH59MWpkRB4yQImTzmnoZ5klUjwuShi2bWLFiI0+FmXNuitAgMBAAGjHjAcMBoG
|
||||
A1UdEQQTMBGCCSouZXZjYy5pb4cEAAAAADANBgkqhkiG9w0BAQUFAAOCAgEAlmh0
|
||||
fR34BT1LTZcTZjFgk3a0/MaZ4zK8zdlwtQFHSlOh46t+TL+jps2bmLu9IlZxLF8E
|
||||
lgSzLu5bWeP8H9HuBgji41RMtwvNUqSFUUMn08Dh/78lFISneqyJZ2CeRzwPYyYs
|
||||
5xi7DBXitFbvtWj5GP3NQaDhsXAPoXtKXuqab3IE6eSwTJOxm5PJnvVdRTFSRQJW
|
||||
W3qQFICQxMm6Oif+yCGZLgROj7sJWOOKJecevtAxYebOCWx5nxbXxAvOY3wiRerG
|
||||
xsS3pT6Q5bVo5OxUjPeRb3Fe969JJEMqGaaGTGGtYP2IVkwarwzKBtcpwgG3NJYv
|
||||
EYG/MEFkpBl6yYJRRB5D7EyPcBRcDHn53sJqnP+Y8Xq40tjfWhxZ9I+N8VWK9nwS
|
||||
a9CKUHlwpB6aT/J1GNMiU2RXhQWgDv8qma6Vik7RH5ZmDMmtG18GERHTNdiGKk9b
|
||||
v5xTphWMHl0NtcGPbPoc8PCrIVkb0frGAX5g6UVCHFfSkiUwLDV1zPhv/tLBa5hX
|
||||
ZzW0wJzs6nDMj9NleyN1B1UY3mbyNKS2Ti9M9NBNEc+IEUdyj36RNmuvDmVNYTZ+
|
||||
eYk28CFfkVT7XQjI1hIpBtu5JqBqic5Kzxg/Xwjmt1izpQBy8C7FFMb0Zs3uvrjo
|
||||
FHk0eyJ/i830ZKCw8Ovx1fgMElZz8AZb/17aeRY=
|
||||
-----END CERTIFICATE-----
|
||||
|
|
@ -1 +0,0 @@
|
|||
subjectAltName=DNS:*.evcc.io,IP:0.0.0.0
|
||||
|
|
@ -1,88 +0,0 @@
|
|||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/andig/evcc/soc/server/auth/sponsor"
|
||||
"github.com/andig/evcc/util"
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
)
|
||||
|
||||
var (
|
||||
Issuer = "evcc.io"
|
||||
TokenExpiry = 365 // days
|
||||
tokenSecret = util.Getenv("JWT_TOKEN_SECRET")
|
||||
)
|
||||
|
||||
var (
|
||||
mux sync.Mutex
|
||||
updated time.Time
|
||||
authorized = make(map[string]bool)
|
||||
)
|
||||
|
||||
const updateInterval = 5 * time.Minute
|
||||
|
||||
type Claims struct {
|
||||
UsernameDeprecated string `json:"username,omitempty"`
|
||||
Username string `json:"usr,omitempty"`
|
||||
SponsorExempt bool `json:"spe,omitempty"`
|
||||
jwt.StandardClaims
|
||||
}
|
||||
|
||||
func keyFunc(token *jwt.Token) (interface{}, error) {
|
||||
return []byte(tokenSecret), nil
|
||||
}
|
||||
|
||||
func AuthorizedToken(claims Claims) (string, error) {
|
||||
claims.Issuer = Issuer
|
||||
claims.IssuedAt = time.Now().Unix()
|
||||
claims.ExpiresAt = time.Now().Add(24 * time.Hour * time.Duration(TokenExpiry)).Unix()
|
||||
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
return token.SignedString([]byte(tokenSecret))
|
||||
}
|
||||
|
||||
func ParseToken(token string) (*Claims, error) {
|
||||
jwt, err := jwt.ParseWithClaims(token, &Claims{}, keyFunc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
claims, ok := jwt.Claims.(*Claims)
|
||||
if !ok {
|
||||
return nil, errors.New("claims invalid")
|
||||
}
|
||||
|
||||
return claims, nil
|
||||
}
|
||||
|
||||
func IsSponsor(login string) (bool, error) {
|
||||
mux.Lock()
|
||||
defer mux.Unlock()
|
||||
|
||||
if _, ok := authorized[login]; !ok {
|
||||
if time.Since(updated) < updateInterval {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
all, err := sponsor.Get(ctx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
updated = time.Now()
|
||||
|
||||
for _, s := range all {
|
||||
authorized[s.Login] = true
|
||||
}
|
||||
}
|
||||
|
||||
_, ok := authorized[login]
|
||||
return ok, nil
|
||||
}
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
package sponsor
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/andig/evcc/util"
|
||||
"github.com/shurcooL/githubv4"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
var client *githubv4.Client
|
||||
|
||||
func init() {
|
||||
token := util.Getenv("GITHUB_TOKEN")
|
||||
|
||||
ts := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: token})
|
||||
tc := oauth2.NewClient(context.Background(), ts)
|
||||
|
||||
client = githubv4.NewClient(tc)
|
||||
}
|
||||
|
||||
// Source: https://github.com/tj/sponsors-api/blob/master/server.go
|
||||
|
||||
// Sponsor model
|
||||
type Sponsor struct {
|
||||
Name string
|
||||
Login string
|
||||
AvatarURL string
|
||||
}
|
||||
|
||||
// sponsorships query: https://docs.github.com/en/graphql/reference/objects
|
||||
type sponsorships struct {
|
||||
Viewer struct {
|
||||
Login string
|
||||
SponsorshipsAsMaintainer struct {
|
||||
PageInfo struct {
|
||||
EndCursor string
|
||||
HasNextPage bool
|
||||
}
|
||||
|
||||
Edges []struct {
|
||||
Node struct {
|
||||
Sponsor Sponsor
|
||||
}
|
||||
Cursor string
|
||||
}
|
||||
} `graphql:"sponsorshipsAsMaintainer(first: 100, after: $cursor, includePrivate: true)"`
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns the list of sponsors
|
||||
func Get(ctx context.Context) ([]Sponsor, error) {
|
||||
var sponsors []Sponsor
|
||||
var q sponsorships
|
||||
var cursor string
|
||||
|
||||
for {
|
||||
err := client.Query(ctx, &q, map[string]interface{}{
|
||||
"cursor": githubv4.String(cursor),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, edge := range q.Viewer.SponsorshipsAsMaintainer.Edges {
|
||||
sponsor := edge.Node.Sponsor
|
||||
sponsors = append(sponsors, sponsor)
|
||||
}
|
||||
|
||||
if !q.Viewer.SponsorshipsAsMaintainer.PageInfo.HasNextPage {
|
||||
break
|
||||
}
|
||||
|
||||
cursor = q.Viewer.SponsorshipsAsMaintainer.PageInfo.EndCursor
|
||||
}
|
||||
|
||||
return sponsors, nil
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
compile "github.com/andig/evcc/server"
|
||||
"github.com/andig/evcc/soc/server/server"
|
||||
"github.com/andig/evcc/soc/server/ui"
|
||||
)
|
||||
|
||||
func main() {
|
||||
log.Printf("soc-server %s (%s)", compile.Version, compile.Commit)
|
||||
|
||||
go ui.Run()
|
||||
go server.Run()
|
||||
|
||||
select {}
|
||||
}
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
|
||||
"github.com/andig/evcc/soc/proto/pb"
|
||||
"github.com/andig/evcc/soc/server/auth"
|
||||
)
|
||||
|
||||
type AuthServer struct {
|
||||
pb.UnimplementedAuthServer
|
||||
}
|
||||
|
||||
func (s *AuthServer) IsAuthorized(ctx context.Context, r *pb.AuthRequest) (*pb.AuthReply, error) {
|
||||
authorized, _, claims, err := isAuthorized(r)
|
||||
|
||||
// track auth requests
|
||||
log.Printf(claims.Subject+": %v", authorized)
|
||||
|
||||
res := &pb.AuthReply{Authorized: authorized}
|
||||
if err == nil {
|
||||
res.Subject = claims.Subject
|
||||
}
|
||||
|
||||
return res, err
|
||||
}
|
||||
|
||||
type tokenizer interface {
|
||||
GetToken() string
|
||||
}
|
||||
|
||||
func isAuthorized(r tokenizer) (bool, string, *auth.Claims, error) {
|
||||
token := r.GetToken()
|
||||
|
||||
claims, err := auth.ParseToken(token)
|
||||
if err != nil {
|
||||
return false, token, claims, err
|
||||
}
|
||||
|
||||
authorized := claims.SponsorExempt
|
||||
if !authorized {
|
||||
authorized, err = auth.IsSponsor(claims.Subject)
|
||||
}
|
||||
|
||||
return authorized, token, claims, err
|
||||
}
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
|
||||
"github.com/andig/evcc/soc/cert/ca"
|
||||
"github.com/andig/evcc/soc/cert/server"
|
||||
"github.com/andig/evcc/soc/proto/pb"
|
||||
"github.com/andig/evcc/util"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
)
|
||||
|
||||
var (
|
||||
port = util.Getenv("GRPC_PORT", "8080")
|
||||
tlsConfig *tls.Config
|
||||
)
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
if tlsConfig, err = loadTLSCredentials(); err != nil {
|
||||
log.Fatalf("cannot load TLS credentials: %v", err)
|
||||
}
|
||||
|
||||
registerMetrics()
|
||||
}
|
||||
|
||||
func loadTLSCredentials() (*tls.Config, error) {
|
||||
certPool := x509.NewCertPool()
|
||||
if !certPool.AppendCertsFromPEM(ca.PEM()) {
|
||||
return nil, fmt.Errorf("failed to add client CA's certificate")
|
||||
}
|
||||
|
||||
// Load server's certificate and private key
|
||||
serverCert, err := server.Certificate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create the credentials and return it
|
||||
config := &tls.Config{
|
||||
Certificates: []tls.Certificate{serverCert},
|
||||
ClientAuth: tls.VerifyClientCertIfGiven,
|
||||
ClientCAs: certPool,
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func Run() {
|
||||
log.Println("grpc:", ":"+port)
|
||||
|
||||
listener, err := net.Listen("tcp", ":"+port)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to listen: %v", err)
|
||||
}
|
||||
|
||||
serverOptions := []grpc.ServerOption{}
|
||||
if tlsConfig != nil {
|
||||
serverOptions = append(serverOptions, grpc.Creds(credentials.NewTLS(tlsConfig)))
|
||||
}
|
||||
grpcServer := grpc.NewServer(serverOptions...)
|
||||
|
||||
pb.RegisterVehicleServer(grpcServer, &VehicleServer{
|
||||
registry: make(map[string][]*VehicleContainer),
|
||||
})
|
||||
pb.RegisterAuthServer(grpcServer, &AuthServer{})
|
||||
|
||||
log.Fatal(grpcServer.Serve(listener))
|
||||
}
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
var (
|
||||
promActiveVehicles *prometheus.GaugeVec
|
||||
)
|
||||
|
||||
func registerMetrics() {
|
||||
promActiveVehicles = prometheus.NewGaugeVec(prometheus.GaugeOpts{
|
||||
Namespace: "soc",
|
||||
Name: "active_vehicles",
|
||||
}, []string{"token", "brand"})
|
||||
|
||||
if err := prometheus.Register(promActiveVehicles); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func updateActiveVehiclesMetric(token, typ string, delta int) {
|
||||
g, err := promActiveVehicles.GetMetricWith(prometheus.Labels{
|
||||
"token": token,
|
||||
"brand": typ,
|
||||
})
|
||||
if err != nil {
|
||||
log.Println("get metrics:", err)
|
||||
return
|
||||
}
|
||||
|
||||
g.Add(float64(delta))
|
||||
}
|
||||
|
|
@ -1,149 +0,0 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"log"
|
||||
"sort"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/andig/evcc/api"
|
||||
"github.com/andig/evcc/internal/vehicle"
|
||||
"github.com/andig/evcc/soc/proto/pb"
|
||||
"github.com/andig/evcc/util/cloud"
|
||||
)
|
||||
|
||||
var vehicleID int64
|
||||
|
||||
type VehicleContainer struct {
|
||||
id int64
|
||||
hash []byte
|
||||
vehicle api.Vehicle
|
||||
}
|
||||
|
||||
type VehicleServer struct {
|
||||
registry map[string][]*VehicleContainer
|
||||
pb.UnimplementedVehicleServer
|
||||
}
|
||||
|
||||
type vehicler interface {
|
||||
tokenizer
|
||||
GetVehicleId() int64
|
||||
}
|
||||
|
||||
func (s *VehicleServer) vehicle(r vehicler) (api.Vehicle, error) {
|
||||
token := r.GetToken()
|
||||
vehicles, ok := s.registry[token]
|
||||
if !ok {
|
||||
return nil, cloud.ErrVehicleNotAvailable
|
||||
}
|
||||
|
||||
id := r.GetVehicleId()
|
||||
for _, c := range vehicles {
|
||||
if c.id == id {
|
||||
return c.vehicle, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, cloud.ErrVehicleNotAvailable
|
||||
}
|
||||
|
||||
func stringMapToInterface(in map[string]string) map[string]interface{} {
|
||||
res := make(map[string]interface{})
|
||||
|
||||
for k, v := range in {
|
||||
res[k] = v
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func (s *VehicleServer) addVehicleToRegistry(token, typ string, config map[string]string, v api.Vehicle) int64 {
|
||||
id := atomic.AddInt64(&vehicleID, 1)
|
||||
|
||||
// sort config keys
|
||||
var keys []string
|
||||
for k := range config {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
// hash config
|
||||
h := sha256.New()
|
||||
_, _ = h.Write([]byte(typ))
|
||||
for _, k := range keys {
|
||||
_, _ = h.Write([]byte(k))
|
||||
_, _ = h.Write([]byte(config[k]))
|
||||
}
|
||||
hash := h.Sum(nil)
|
||||
|
||||
// find vehicle by hash and update it
|
||||
for _, c := range s.registry[token] {
|
||||
if bytes.Equal(c.hash, hash) {
|
||||
c.vehicle = v
|
||||
c.id = id
|
||||
return id
|
||||
}
|
||||
}
|
||||
|
||||
// register new vehicle
|
||||
c := VehicleContainer{
|
||||
id: id,
|
||||
hash: hash,
|
||||
vehicle: v,
|
||||
}
|
||||
s.registry[token] = append(s.registry[token], &c)
|
||||
|
||||
h.Reset()
|
||||
_, _ = h.Write([]byte(token))
|
||||
thash := fmt.Sprintf("%x", h.Sum(nil))
|
||||
|
||||
updateActiveVehiclesMetric(thash, typ, 1)
|
||||
|
||||
return id
|
||||
}
|
||||
|
||||
func (s *VehicleServer) New(ctx context.Context, r *pb.NewRequest) (*pb.NewReply, error) {
|
||||
authorized, token, claims, err := isAuthorized(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !authorized {
|
||||
return nil, cloud.ErrNotAuthorized
|
||||
}
|
||||
|
||||
typ := r.GetType()
|
||||
config := r.GetConfig()
|
||||
|
||||
// track vehicle create
|
||||
log.Println(claims.Subject+":", typ)
|
||||
|
||||
v, err := vehicle.NewFromConfig(typ, stringMapToInterface(config))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
id := s.addVehicleToRegistry(token, typ, config, v)
|
||||
|
||||
res := pb.NewReply{
|
||||
VehicleId: id,
|
||||
}
|
||||
|
||||
return &res, nil
|
||||
}
|
||||
|
||||
func (s *VehicleServer) SoC(ctx context.Context, r *pb.SoCRequest) (*pb.SoCReply, error) {
|
||||
v, err := s.vehicle(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
soc, err := v.SoC()
|
||||
res := pb.SoCReply{
|
||||
Soc: soc,
|
||||
}
|
||||
|
||||
return &res, err
|
||||
}
|
||||
|
|
@ -1,131 +0,0 @@
|
|||
<!doctype html>
|
||||
<html lang="en" class="h-100">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>evcc Sponsorship</title>
|
||||
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta3/dist/css/bootstrap.min.css" rel="stylesheet"
|
||||
integrity="sha384-eOJMYsd53ii+scO/bJGFsiCZc+5NDVN2yr8+0RDqr0Ql0h+rP48ckxlpbzKgwra6" crossorigin="anonymous">
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta3/dist/js/bootstrap.bundle.min.js"
|
||||
integrity="sha384-JEW9xMcG8R+pH31jmWH6WWP0WintQrMb4s7ZOdauHnUtxwoG2vI5DkLtS3qm9Ekf" crossorigin="anonymous">
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.bd-placeholder-img {
|
||||
font-size: 1.125rem;
|
||||
text-anchor: middle;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.bd-placeholder-img-lg {
|
||||
font-size: 3.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<style>
|
||||
/* Custom default button */
|
||||
.btn-secondary,
|
||||
.btn-secondary:hover,
|
||||
.btn-secondary:focus {
|
||||
color: #333;
|
||||
text-shadow: none;
|
||||
/* Prevent inheritance from `body` */
|
||||
}
|
||||
|
||||
body {
|
||||
text-shadow: 0 .05rem .1rem rgba(0, 0, 0, .5);
|
||||
box-shadow: inset 0 0 5rem rgba(0, 0, 0, .5);
|
||||
}
|
||||
|
||||
.cover-container {
|
||||
max-width: 42em;
|
||||
}
|
||||
|
||||
.nav-masthead .nav-link {
|
||||
padding: .25rem 0;
|
||||
font-weight: 700;
|
||||
color: rgba(255, 255, 255, .5);
|
||||
background-color: transparent;
|
||||
border-bottom: .25rem solid transparent;
|
||||
}
|
||||
|
||||
.nav-masthead .nav-link:hover,
|
||||
.nav-masthead .nav-link:focus {
|
||||
border-bottom-color: rgba(255, 255, 255, .25);
|
||||
}
|
||||
|
||||
.nav-masthead .nav-link+.nav-link {
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
.nav-masthead .active {
|
||||
color: #fff;
|
||||
border-bottom-color: #fff;
|
||||
}
|
||||
|
||||
.bd-placeholder-img {
|
||||
font-size: 1.125rem;
|
||||
text-anchor: middle;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.bd-placeholder-img-lg {
|
||||
font-size: 3.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body class="d-flex h-100 text-center text-white bg-dark">
|
||||
<div class="cover-container d-flex w-100 h-100 p-3 mx-auto flex-column">
|
||||
|
||||
<header class="mb-auto">
|
||||
<div>
|
||||
<h3 class="float-md-start mb-0">evcc Sponsorship</h3>
|
||||
<nav class="nav nav-masthead justify-content-center float-md-end">
|
||||
<a class="nav-link active" aria-current="page" href="/">Home</a>
|
||||
<a class="nav-link" href="/privacy">Impressum & Datenschutz</a>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="px-3">
|
||||
{{if .Content}}
|
||||
{{.Content}}
|
||||
{{else}}
|
||||
<h1>Willkommen</h1>
|
||||
<p class="lead">
|
||||
Die Freischaltung der Sonderfunktionen für evcc Sponsoren erfordert eine einmalige Anmeldung um evcc mit dem
|
||||
Github Konto des evcc Sponsors zu verknüpfen. Mit der Verknüpfung wird das Registrierungstoken erzeugt.
|
||||
</p>
|
||||
<p class="lead">
|
||||
Das Registrierungstoken gibt Zugang zu den folgenden Funktionen:
|
||||
<p>Nutzung des evcc-soc Moduls mit OpenWB</p>
|
||||
<p>Nutzung des evcc Easee Moduls</p>
|
||||
</p>
|
||||
<p class="lead">
|
||||
<a href="/login" class="btn btn-lg btn-secondary fw-bold border-white bg-white">Anmelden</a>
|
||||
</p>
|
||||
{{end}}
|
||||
</main>
|
||||
|
||||
<footer class="mt-auto text-white-50">
|
||||
<p>Cover template by <a href="https://twitter.com/mdo" class="text-white">@mdo</a>.</p>
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
@ -1,220 +0,0 @@
|
|||
<!doctype html>
|
||||
<html lang="en" class="h-100">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Impressum und Datenschutzerklärung</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Impressum</h1>
|
||||
<h2>Diensteanbieter</h2>
|
||||
<p>Andreas Götz</p>
|
||||
<p>Gubiner Str. 15</p>
|
||||
<p>30880 Laatzen</p>
|
||||
|
||||
<h3>Kontaktmöglichkeiten</h3>
|
||||
<p>E-Mail-Adresse: <a href="mailto:info@evcc.io">info@evcc.io</a></p>
|
||||
|
||||
<h1>Datenschutzerklärung</h1>
|
||||
<h2>Einleitung</h2>
|
||||
<p>Mit der folgenden Datenschutzerklärung möchten wir Sie darüber aufklären, welche Arten Ihrer personenbezogenen
|
||||
Daten (nachfolgend auch kurz als "Daten“ bezeichnet) wir zu welchen Zwecken und in welchem Umfang verarbeiten. Die
|
||||
Datenschutzerklärung gilt für alle von uns durchgeführten Verarbeitungen personenbezogener Daten, sowohl im
|
||||
Rahmen der Erbringung unserer Leistungen als auch insbesondere auf unseren Webseiten, in mobilen Applikationen sowie
|
||||
innerhalb externer Onlinepräsenzen, wie z.B. unserer Social-Media-Profile (nachfolgend zusammenfassend
|
||||
bezeichnet als "Onlineangebot“).</p>
|
||||
<p>Die verwendeten Begriffe sind nicht geschlechtsspezifisch.</p>
|
||||
<p>Stand: 26. April 2021</p>
|
||||
<h2>Inhaltsübersicht</h2>
|
||||
<ul class="index">
|
||||
<li><a class="index-link" href="#m14">Einleitung</a></li>
|
||||
<li><a class="index-link" href="#m3">Verantwortlicher</a></li>
|
||||
<li><a class="index-link" href="#mOverview">Übersicht der Verarbeitungen</a></li>
|
||||
<li><a class="index-link" href="#m13">Maßgebliche Rechtsgrundlagen</a></li>
|
||||
<li><a class="index-link" href="#m27">Sicherheitsmaßnahmen</a></li>
|
||||
<li><a class="index-link" href="#m25">Übermittlung von personenbezogenen Daten</a></li>
|
||||
<li><a class="index-link" href="#m24">Datenverarbeitung in Drittländern</a></li>
|
||||
<li><a class="index-link" href="#m317">Geschäftliche Leistungen</a></li>
|
||||
<li><a class="index-link" href="#m29">Bereitstellung des Onlineangebotes</a></li>
|
||||
<li><a class="index-link" href="#m12">Löschung von Daten</a></li>
|
||||
<li><a class="index-link" href="#m15">Änderung und Aktualisierung der Datenschutzerklärung</a></li>
|
||||
<li><a class="index-link" href="#m10">Rechte der betroffenen Personen</a></li>
|
||||
<li><a class="index-link" href="#m42">Begriffsdefinitionen</a></li>
|
||||
</ul>
|
||||
<h2 id="m3">Verantwortlicher</h2>
|
||||
<p>Andreas Götz<br>Gubiner Str. 15<br>30880 Laatzen</p>
|
||||
<p>E-Mail-Adresse: <a href="mailto:info@evcc.io">info@evcc.io</a>.</p>
|
||||
<h2 id="mOverview">Übersicht der Verarbeitungen</h2>
|
||||
<p>Die nachfolgende Übersicht fasst die Arten der verarbeiteten Daten und die Zwecke ihrer Verarbeitung zusammen und
|
||||
verweist auf die betroffenen Personen.</p>
|
||||
<h3>Arten der verarbeiteten Daten</h3>
|
||||
<ul>
|
||||
<li>Bestandsdaten (Github Profil und Sponsorship Level).</li>
|
||||
<li>Meta-/Kommunikationsdaten (IP-Adressen, Hostnamen).</li>
|
||||
<li>Vertragsdaten (Fahrzeugtyp und Zugangsdaten für Fahrzeug-APIs).</li>
|
||||
</ul>
|
||||
<h3>Kategorien betroffener Personen</h3>
|
||||
<ul>
|
||||
<li>Interessenten</li>
|
||||
<li>Kunden</li>
|
||||
<li>Nutzer</li>
|
||||
</ul>
|
||||
<h3>Zwecke der Verarbeitung</h3>
|
||||
<ul>
|
||||
<li>Bereitstellung unseres Onlineangebotes.</li>
|
||||
<li>Verwaltung und Beantwortung von Anfragen.</li>
|
||||
</ul>
|
||||
<h3 id="m13">Maßgebliche Rechtsgrundlagen</h3>
|
||||
<p>Im Folgenden erhalten Sie eine Übersicht der Rechtsgrundlagen der DSGVO, auf deren Basis wir personenbezogenen
|
||||
Daten verarbeiten. Bitte nehmen Sie zur Kenntnis, dass neben den Regelungen der DSGVO nationale Datenschutzvorgaben
|
||||
in Ihrem bzw. unserem Wohn- oder Sitzland gelten können. Sollten ferner im Einzelfall speziellere Rechtsgrundlagen
|
||||
maßgeblich sein, teilen wir Ihnen diese in der Datenschutzerklärung mit.</p>
|
||||
<ul>
|
||||
<li><strong>Einwilligung (Art. 6 Abs. 1 S. 1 lit. a. DSGVO)</strong> - Die betroffene Person hat ihre
|
||||
Einwilligung in die Verarbeitung der sie betreffenden personenbezogenen Daten für einen spezifischen Zweck oder
|
||||
mehrere bestimmte Zwecke gegeben.</li>
|
||||
<li><strong>Vertragserfüllung und vorvertragliche Anfragen (Art. 6 Abs. 1 S. 1 lit. b. DSGVO)</strong> - Die
|
||||
Verarbeitung ist für die Erfüllung eines Vertrags, dessen Vertragspartei die betroffene Person ist, oder zur
|
||||
Durchführung vorvertraglicher Maßnahmen erforderlich, die auf Anfrage der betroffenen Person erfolgen.</li>
|
||||
<li><strong>Rechtliche Verpflichtung (Art. 6 Abs. 1 S. 1 lit. c. DSGVO)</strong> - Die Verarbeitung ist zur
|
||||
Erfüllung einer rechtlichen Verpflichtung erforderlich, der der Verantwortliche unterliegt.</li>
|
||||
<li><strong>Berechtigte Interessen (Art. 6 Abs. 1 S. 1 lit. f. DSGVO)</strong> - Die Verarbeitung ist zur
|
||||
Wahrung der berechtigten Interessen des Verantwortlichen oder eines Dritten erforderlich, sofern nicht die
|
||||
Interessen oder Grundrechte und Grundfreiheiten der betroffenen Person, die den Schutz personenbezogener Daten
|
||||
erfordern, überwiegen.</li>
|
||||
</ul>
|
||||
<p><strong>Nationale Datenschutzregelungen in Deutschland</strong>: Zusätzlich zu den Datenschutzregelungen der
|
||||
Datenschutz-Grundverordnung gelten nationale Regelungen zum Datenschutz in Deutschland. Hierzu gehört
|
||||
insbesondere das Gesetz zum Schutz vor Missbrauch personenbezogener Daten bei der Datenverarbeitung
|
||||
(Bundesdatenschutzgesetz – BDSG). Das BDSG enthält insbesondere Spezialregelungen zum Recht auf Auskunft, zum Recht
|
||||
auf Löschung, zum Widerspruchsrecht, zur Verarbeitung besonderer Kategorien personenbezogener Daten, zur
|
||||
Verarbeitung für andere Zwecke und zur Übermittlung sowie automatisierten Entscheidungsfindung im Einzelfall
|
||||
einschließlich Profiling.
|
||||
Des Weiteren regelt es die Datenverarbeitung für Zwecke des Beschäftigungsverhältnisses (§ 26 BDSG), insbesondere im
|
||||
Hinblick auf die Begründung, Durchführung oder Beendigung von Beschäftigungsverhältnissen sowie die Einwilligung
|
||||
von Beschäftigten. Ferner können Landesdatenschutzgesetze der einzelnen Bundesländer zur Anwendung gelangen.</p>
|
||||
<h2 id="m27">Sicherheitsmaßnahmen</h2>
|
||||
<p>Wir treffen nach Maßgabe der gesetzlichen Vorgaben unter Berücksichtigung des Stands der Technik, der
|
||||
Implementierungskosten und der Art, des Umfangs, der Umstände und der Zwecke der Verarbeitung sowie der
|
||||
unterschiedlichen Eintrittswahrscheinlichkeiten und des Ausmaßes der Bedrohung der Rechte und Freiheiten
|
||||
natürlicher Personen geeignete technische und organisatorische Maßnahmen, um ein dem Risiko angemessenes
|
||||
Schutzniveau zu gewährleisten.</p>
|
||||
<p>Zu den Maßnahmen gehören insbesondere die Sicherung der Vertraulichkeit, Integrität und Verfügbarkeit von Daten
|
||||
durch Kontrolle des physischen und elektronischen Zugangs zu den Daten als auch des sie betreffenden Zugriffs, der
|
||||
Eingabe, der Weitergabe, der Sicherung der Verfügbarkeit und ihrer Trennung. Des Weiteren haben wir Verfahren
|
||||
eingerichtet, die eine Wahrnehmung von Betroffenenrechten, die Löschung von Daten und Reaktionen auf die
|
||||
Gefährdung der Daten gewährleisten. Ferner berücksichtigen wir den Schutz personenbezogener Daten bereits bei der
|
||||
Entwicklung bzw. Auswahl von Hardware, Software sowie Verfahren entsprechend dem Prinzip des Datenschutzes, durch
|
||||
Technikgestaltung und durch datenschutzfreundliche Voreinstellungen.</p>
|
||||
<h2 id="m25">Übermittlung von personenbezogenen Daten</h2>
|
||||
<p>Im Rahmen unserer Verarbeitung von personenbezogenen Daten kommt es vor, dass die Daten an andere Stellen,
|
||||
Unternehmen, rechtlich selbstständige Organisationseinheiten oder Personen übermittelt oder sie ihnen gegenüber
|
||||
offengelegt werden. Zu den Empfängern dieser Daten können z.B. mit IT-Aufgaben beauftragte Dienstleister oder
|
||||
Anbieter von Diensten und Inhalten, die in eine Webseite eingebunden werden, gehören. In solchen Fall beachten
|
||||
wir die gesetzlichen Vorgaben und schließen insbesondere entsprechende Verträge bzw. Vereinbarungen, die dem Schutz
|
||||
Ihrer Daten dienen, mit den Empfängern Ihrer Daten ab.</p>
|
||||
<h2 id="m24">Datenverarbeitung in Drittländern</h2>
|
||||
<p>Sofern wir Daten in einem Drittland (d.h., außerhalb der Europäischen Union (EU), des Europäischen
|
||||
Wirtschaftsraums (EWR)) verarbeiten oder die Verarbeitung im Rahmen der Inanspruchnahme von Diensten Dritter oder
|
||||
der Offenlegung bzw. Übermittlung von Daten an andere Personen, Stellen oder Unternehmen stattfindet, erfolgt dies
|
||||
nur im Einklang mit den gesetzlichen Vorgaben. </p>
|
||||
<p>Vorbehaltlich ausdrücklicher Einwilligung oder vertraglich oder gesetzlich erforderlicher Übermittlung
|
||||
verarbeiten oder lassen wir die Daten nur in Drittländern mit einem anerkannten Datenschutzniveau, vertraglichen
|
||||
Verpflichtung durch sogenannte Standardschutzklauseln der EU-Kommission, beim Vorliegen von Zertifizierungen oder
|
||||
verbindlicher internen Datenschutzvorschriften verarbeiten (Art. 44 bis 49 DSGVO, Informationsseite der
|
||||
EU-Kommission: <a
|
||||
href="https://ec.europa.eu/info/law/law-topic/data-protection/international-dimension-data-protection_de"
|
||||
target="_blank">https://ec.europa.eu/info/law/law-topic/data-protection/international-dimension-data-protection_de</a>).
|
||||
</p>
|
||||
<h2 id="m29">Bereitstellung des Onlineangebotes</h2>
|
||||
<p>Das Onlineangebote wird auf Servern von Prospect One (Karmelicka 5/5, Krakow, 31-133, Polen) betrieben. Wir
|
||||
verweisen
|
||||
auf die entsprechende <a href="https://appfleet.com/privacy-policy"> Datenschutzerklärung</a>
|
||||
</p>
|
||||
<p>
|
||||
Wir verwenden Github für die Bereitstellung der Sponsoring Funktion sowie für die Generierung des Login Tokens.
|
||||
Wir verweisen auf die entsprechende <a
|
||||
href="https://docs.github.com/en/github/site-policy/github-privacy-statement"> Datenschutzerklärung</a>
|
||||
</p>
|
||||
<h2 id="m12">Löschung von Daten</h2>
|
||||
<p>Die von uns verarbeiteten Daten werden nach Maßgabe der gesetzlichen Vorgaben gelöscht, sobald deren zur
|
||||
Verarbeitung erlaubten Einwilligungen widerrufen werden oder sonstige Erlaubnisse entfallen (z.B., wenn der Zweck
|
||||
der Verarbeitung dieser Daten entfallen ist oder sie für den Zweck nicht erforderlich sind).</p>
|
||||
<p>Sofern die Daten nicht gelöscht werden, weil sie für andere und gesetzlich zulässige Zwecke erforderlich sind,
|
||||
wird deren Verarbeitung auf diese Zwecke beschränkt. D.h., die Daten werden gesperrt und nicht für andere Zwecke
|
||||
verarbeitet. Das gilt z.B. für Daten, die aus handels- oder steuerrechtlichen Gründen aufbewahrt werden müssen
|
||||
oder deren Speicherung zur Geltendmachung, Ausübung oder Verteidigung von Rechtsansprüchen oder zum Schutz der
|
||||
Rechte einer anderen natürlichen oder juristischen Person erforderlich ist.</p>
|
||||
<p>Unsere Datenschutzhinweise können ferner weitere Angaben zu der Aufbewahrung und Löschung von Daten beinhalten,
|
||||
die für die jeweiligen Verarbeitungen vorrangig gelten.</p>
|
||||
<h2 id="m15">Änderung und Aktualisierung der Datenschutzerklärung</h2>
|
||||
<p>Wir bitten Sie, sich regelmäßig über den Inhalt unserer Datenschutzerklärung zu informieren. Wir passen die
|
||||
Datenschutzerklärung an, sobald die Änderungen der von uns durchgeführten Datenverarbeitungen dies erforderlich
|
||||
machen. Wir informieren Sie, sobald durch die Änderungen eine Mitwirkungshandlung Ihrerseits (z.B. Einwilligung)
|
||||
oder eine sonstige individuelle Benachrichtigung erforderlich wird.</p>
|
||||
<p>Sofern wir in dieser Datenschutzerklärung Adressen und Kontaktinformationen von Unternehmen und Organisationen
|
||||
angeben, bitten wir zu beachten, dass die Adressen sich über die Zeit ändern können und bitten die Angaben vor
|
||||
Kontaktaufnahme zu prüfen.</p>
|
||||
<h2 id="m10">Rechte der betroffenen Personen</h2>
|
||||
<p>Ihnen stehen als Betroffene nach der DSGVO verschiedene Rechte zu, die sich insbesondere aus Art. 15 bis 21 DSGVO
|
||||
ergeben:</p>
|
||||
<ul>
|
||||
<li><strong>Widerspruchsrecht: Sie haben das Recht, aus Gründen, die sich aus Ihrer besonderen Situation
|
||||
ergeben, jederzeit gegen die Verarbeitung der Sie betreffenden personenbezogenen Daten, die aufgrund von Art. 6
|
||||
Abs. 1 lit. e oder f DSGVO erfolgt, Widerspruch einzulegen; dies gilt auch für ein auf diese Bestimmungen
|
||||
gestütztes Profiling. Werden die Sie betreffenden personenbezogenen Daten verarbeitet, um Direktwerbung
|
||||
zu betreiben, haben Sie das Recht, jederzeit Widerspruch gegen die Verarbeitung der Sie betreffenden
|
||||
personenbezogenen Daten zum Zwecke derartiger Werbung einzulegen; dies gilt auch für das Profiling,
|
||||
soweit es mit solcher Direktwerbung in Verbindung steht.</strong></li>
|
||||
<li><strong>Widerrufsrecht bei Einwilligungen:</strong> Sie haben das Recht, erteilte Einwilligungen jederzeit
|
||||
zu widerrufen.</li>
|
||||
<li><strong>Auskunftsrecht:</strong> Sie haben das Recht, eine Bestätigung darüber zu verlangen, ob betreffende
|
||||
Daten verarbeitet werden und auf Auskunft über diese Daten sowie auf weitere Informationen und Kopie der
|
||||
Daten entsprechend den gesetzlichen Vorgaben.</li>
|
||||
<li><strong>Recht auf Berichtigung:</strong> Sie haben entsprechend den gesetzlichen Vorgaben das Recht, die
|
||||
Vervollständigung der Sie betreffenden Daten oder die Berichtigung der Sie betreffenden unrichtigen Daten zu
|
||||
verlangen.</li>
|
||||
<li><strong>Recht auf Löschung und Einschränkung der Verarbeitung:</strong> Sie haben nach Maßgabe der
|
||||
gesetzlichen Vorgaben das Recht, zu verlangen, dass Sie betreffende Daten unverzüglich gelöscht werden, bzw.
|
||||
alternativ nach Maßgabe der gesetzlichen Vorgaben eine Einschränkung der Verarbeitung der Daten zu verlangen.</li>
|
||||
<li><strong>Recht auf Datenübertragbarkeit:</strong> Sie haben das Recht, Sie betreffende Daten, die Sie uns
|
||||
bereitgestellt haben, nach Maßgabe der gesetzlichen Vorgaben in einem strukturierten, gängigen und
|
||||
maschinenlesbaren Format zu erhalten oder deren Übermittlung an einen anderen Verantwortlichen zu fordern.
|
||||
</li>
|
||||
<li><strong>Beschwerde bei Aufsichtsbehörde:</strong> Sie haben unbeschadet eines anderweitigen
|
||||
verwaltungsrechtlichen oder gerichtlichen Rechtsbehelfs das Recht auf Beschwerde bei einer Aufsichtsbehörde,
|
||||
insbesondere in dem Mitgliedstaat ihres gewöhnlichen Aufenthaltsorts, ihres Arbeitsplatzes oder des Orts des
|
||||
mutmaßlichen Verstoßes, wenn Sie der Ansicht sind, dass die Verarbeitung der Sie betreffenden
|
||||
personenbezogenen Daten gegen die Vorgaben der DSGVO verstößt.</li>
|
||||
</ul>
|
||||
<h2 id="m42">Begriffsdefinitionen</h2>
|
||||
<p>In diesem Abschnitt erhalten Sie eine Übersicht über die in dieser Datenschutzerklärung verwendeten
|
||||
Begrifflichkeiten. Viele der Begriffe sind dem Gesetz entnommen und vor allem im Art. 4 DSGVO definiert. Die
|
||||
gesetzlichen Definitionen sind verbindlich. Die nachfolgenden Erläuterungen sollen dagegen vor allem dem
|
||||
Verständnis dienen. Die Begriffe sind alphabetisch sortiert.</p>
|
||||
<ul class="glossary">
|
||||
<li><strong>Personenbezogene Daten:</strong> "Personenbezogene Daten“ sind alle Informationen, die sich auf eine
|
||||
identifizierte oder identifizierbare natürliche Person (im Folgenden "betroffene Person“) beziehen; als
|
||||
identifizierbar wird eine natürliche Person angesehen, die direkt oder indirekt, insbesondere mittels
|
||||
Zuordnung zu einer Kennung wie einem Namen, zu einer Kennnummer, zu Standortdaten, zu einer Online-Kennung (z.B.
|
||||
Cookie) oder zu einem oder mehreren besonderen Merkmalen identifiziert werden kann, die Ausdruck der physischen,
|
||||
physiologischen, genetischen, psychischen, wirtschaftlichen, kulturellen oder sozialen Identität dieser
|
||||
natürlichen Person sind. </li>
|
||||
<li><strong>Verantwortlicher:</strong> Als "Verantwortlicher“ wird die natürliche oder juristische Person,
|
||||
Behörde, Einrichtung oder andere Stelle, die allein oder gemeinsam mit anderen über die Zwecke und Mittel der
|
||||
Verarbeitung von personenbezogenen Daten entscheidet, bezeichnet. </li>
|
||||
<li><strong>Verarbeitung:</strong> "Verarbeitung" ist jeder mit oder ohne Hilfe automatisierter Verfahren
|
||||
ausgeführte Vorgang oder jede solche Vorgangsreihe im Zusammenhang mit personenbezogenen Daten. Der Begriff
|
||||
reicht weit und umfasst praktisch jeden Umgang mit Daten, sei es das Erheben, das Auswerten, das Speichern,
|
||||
das Übermitteln oder das Löschen. </li>
|
||||
</ul>
|
||||
<p class="seal"><a href="https://datenschutz-generator.de/?l=de"
|
||||
title="Rechtstext von Dr. Schwenke - für weitere Informationen bitte anklicken." target="_blank"
|
||||
rel="noopener noreferrer nofollow">Erstellt mit kostenlosem Datenschutz-Generator.de von Dr. Thomas
|
||||
Schwenke</a>
|
||||
</p>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
@ -1,297 +0,0 @@
|
|||
package ui
|
||||
|
||||
import (
|
||||
"context"
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/http/pprof"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/andig/evcc/internal/vehicle"
|
||||
"github.com/andig/evcc/soc/server/auth"
|
||||
"github.com/andig/evcc/util"
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"github.com/thoas/go-funk"
|
||||
"golang.org/x/crypto/acme/autocert"
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/oauth2/github"
|
||||
)
|
||||
|
||||
//go:embed index.html
|
||||
var indexHtml string
|
||||
|
||||
//go:embed privacy.html
|
||||
var privacyHtml string
|
||||
|
||||
var (
|
||||
// login ui and callback
|
||||
redirectURL = util.Getenv("REDIRECT_URL")
|
||||
|
||||
// SSL termination
|
||||
sslCertDir = util.Getenv("SSL_CERT_DIR", "certs")
|
||||
|
||||
// github oauth application
|
||||
clientID = util.Getenv("GITHUB_CLIENT_ID")
|
||||
clientSecret = util.Getenv("GITHUB_CLIENT_SECRET")
|
||||
)
|
||||
|
||||
var (
|
||||
indexTpl, privacyTpl *template.Template
|
||||
|
||||
oauthState = randomString()
|
||||
oauthConfig *oauth2.Config
|
||||
)
|
||||
|
||||
const (
|
||||
ProfileURL = "https://api.github.com/user"
|
||||
EmailURL = "https://api.github.com/user/emails"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
ID int `json:"id"`
|
||||
Email string `json:"email"`
|
||||
Bio string `json:"bio"`
|
||||
Name string `json:"name"`
|
||||
Login string `json:"login"`
|
||||
Picture string `json:"avatar_url"`
|
||||
Location string `json:"location"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
clientID = util.Getenv("GITHUB_CLIENT_ID")
|
||||
clientSecret = util.Getenv("GITHUB_CLIENT_SECRET")
|
||||
|
||||
oauthConfig = &oauth2.Config{
|
||||
RedirectURL: redirectURL,
|
||||
ClientID: clientID,
|
||||
ClientSecret: clientSecret,
|
||||
Scopes: []string{},
|
||||
Endpoint: github.Endpoint,
|
||||
}
|
||||
|
||||
indexTpl = template.Must(template.New("index").Parse(indexHtml))
|
||||
privacyTpl = template.Must(template.New("privacy").Parse(privacyHtml))
|
||||
}
|
||||
|
||||
func randomString() string {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
chars := []rune("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")
|
||||
length := 16
|
||||
var b strings.Builder
|
||||
for i := 0; i < length; i++ {
|
||||
b.WriteRune(chars[rand.Intn(len(chars))])
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func handleMain(w http.ResponseWriter, r *http.Request) {
|
||||
_ = indexTpl.Execute(w, nil)
|
||||
}
|
||||
|
||||
func handlePrivacy(w http.ResponseWriter, r *http.Request) {
|
||||
_ = privacyTpl.Execute(w, nil)
|
||||
}
|
||||
|
||||
func templateError(w http.ResponseWriter, r *http.Request, err string) {
|
||||
_ = indexTpl.Execute(w, map[string]interface{}{
|
||||
"Content": template.HTML(`
|
||||
<h1>Fehler</h1>
|
||||
<p class="lead">` + err + `</p>`),
|
||||
})
|
||||
}
|
||||
|
||||
func handleLogin(w http.ResponseWriter, r *http.Request) {
|
||||
url := oauthConfig.AuthCodeURL(oauthState)
|
||||
http.Redirect(w, r, url, http.StatusTemporaryRedirect)
|
||||
}
|
||||
|
||||
func handleCallback(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Query().Get("error") != "" {
|
||||
err := r.URL.Query().Get("error_description")
|
||||
templateError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
user, err := getUserInfo(r.FormValue("state"), r.FormValue("code"))
|
||||
if err != nil {
|
||||
templateError(w, r, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
authorized, err := auth.IsSponsor(user.Login)
|
||||
if err != nil {
|
||||
templateError(w, r, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
authMap := map[bool]string{false: "rejected", true: "authorized"}
|
||||
log.Println(user.Login+":", authMap[authorized])
|
||||
|
||||
if !authorized {
|
||||
_ = indexTpl.Execute(w, map[string]interface{}{
|
||||
"Content": template.HTML(`
|
||||
<h1>Inaktiv</h1>
|
||||
<p class="lead">Github Sponsorship für evcc ist inaktiv. Um erweiterte Funktionen zu nutzen, ist es notwendig, evcc zu unterstützen.</p>
|
||||
<p class="lead">evcc bei Github <a href="https://github.com/sponsors/andig" class="text-warning">unterstützen</a>.</p>`),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
claims := auth.Claims{
|
||||
Username: user.Name,
|
||||
StandardClaims: jwt.StandardClaims{
|
||||
Subject: user.Login,
|
||||
},
|
||||
}
|
||||
|
||||
jwt, err := auth.AuthorizedToken(claims)
|
||||
if err != nil {
|
||||
templateError(w, r, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
_ = indexTpl.Execute(w, map[string]interface{}{
|
||||
"Content": template.HTML(fmt.Sprintf(`
|
||||
<h1>Aktiv</h1>
|
||||
<p class="lead">Github Sponsorship für evcc ist aktiv. Das folgende Registrierungstoken kann für den Zugriff auf evcc genutzt werden.</p>
|
||||
<p class="lead">Der Code ist %d Tage gültig und kann jederzeit neu erzeugt werden.</p>
|
||||
<p class="lead"><code>`+jwt+`</code></p>`, auth.TokenExpiry),
|
||||
)})
|
||||
}
|
||||
|
||||
func getUserInfo(state string, code string) (*User, error) {
|
||||
if state != oauthState {
|
||||
return nil, fmt.Errorf("invalid oauth state")
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
token, err := oauthConfig.Exchange(ctx, code)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("code exchange failed: %s", err.Error())
|
||||
}
|
||||
|
||||
var user User
|
||||
req, err := http.NewRequest("GET", ProfileURL, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Add("Authorization", "Bearer "+token.AccessToken)
|
||||
response, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("GitHub API responded with a %d trying to fetch user information", response.StatusCode)
|
||||
}
|
||||
|
||||
bits, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(bits, &user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
var typeFilter = []string{"custom", "cloud", "niu", "tesla"}
|
||||
|
||||
var typeMap = map[string]string{
|
||||
"bmw": "BMW",
|
||||
"citroen": "Citroën",
|
||||
"skoda": "Škoda",
|
||||
"enyaq": "Škoda Enyaq",
|
||||
"vw": "VW",
|
||||
"id": "VW ID",
|
||||
}
|
||||
|
||||
type typeDesc struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
func handleVehicles(w http.ResponseWriter, r *http.Request) {
|
||||
types := make([]typeDesc, 0)
|
||||
|
||||
for _, id := range vehicle.Types() {
|
||||
if funk.ContainsString(typeFilter, id) {
|
||||
continue
|
||||
}
|
||||
|
||||
vt := typeDesc{ID: id, Name: strings.Title(id)}
|
||||
if name, ok := typeMap[id]; ok {
|
||||
vt.Name = name
|
||||
}
|
||||
|
||||
types = append(types, vt)
|
||||
}
|
||||
|
||||
sort.Slice(types, func(i, j int) bool {
|
||||
return strings.Compare(types[i].Name, types[j].Name) < 0
|
||||
})
|
||||
|
||||
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
|
||||
_ = json.NewEncoder(w).Encode(types)
|
||||
}
|
||||
|
||||
func Run() {
|
||||
mux := &http.ServeMux{}
|
||||
mux.HandleFunc("/", handleMain)
|
||||
mux.HandleFunc("/privacy", handlePrivacy)
|
||||
mux.HandleFunc("/login", handleLogin)
|
||||
mux.HandleFunc("/callback", handleCallback)
|
||||
mux.HandleFunc("/api/vehicles", handleVehicles)
|
||||
|
||||
mux.Handle("/metrics", promhttp.Handler())
|
||||
|
||||
mux.HandleFunc("/debug/pprof/", pprof.Index)
|
||||
mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
|
||||
// mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
|
||||
// mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
|
||||
// mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
|
||||
|
||||
s := &http.Server{
|
||||
Addr: ":http",
|
||||
Handler: mux,
|
||||
}
|
||||
|
||||
if sslHosts := strings.TrimSpace(os.Getenv("SSL_HOSTS")); sslHosts != "" {
|
||||
log.Println("sslHosts:", sslHosts)
|
||||
log.Println("sslCertDir:", sslCertDir)
|
||||
|
||||
certManager := autocert.Manager{
|
||||
Prompt: autocert.AcceptTOS,
|
||||
HostPolicy: autocert.HostWhitelist(strings.Split(sslHosts, " ")...),
|
||||
Cache: autocert.DirCache(sslCertDir),
|
||||
}
|
||||
|
||||
go func() {
|
||||
log.Fatal(http.ListenAndServe(":http", certManager.HTTPHandler(nil)))
|
||||
}()
|
||||
|
||||
s.Addr = ":https"
|
||||
s.TLSConfig = certManager.TLSConfig()
|
||||
|
||||
log.Fatal(s.ListenAndServeTLS("", ""))
|
||||
} else {
|
||||
log.Fatal(s.ListenAndServe())
|
||||
}
|
||||
}
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIE1DCCArwCCQC/TbpAPhcOQTANBgkqhkiG9w0BAQsFADAsMQswCQYDVQQGEwJE
|
||||
RTEdMBsGCSqGSIb3DQEJARYOY3B1aWRsZUBnbXguZGUwHhcNMjEwMzMxMDc1ODAz
|
||||
WhcNMjIwMzMxMDc1ODAzWjAsMQswCQYDVQQGEwJERTEdMBsGCSqGSIb3DQEJARYO
|
||||
Y3B1aWRsZUBnbXguZGUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC+
|
||||
noDTXbidh/5RFfsLlMJbfMn4Vu91oJb4MM41RQM8UOr3SRU/ZzcoLfWJe2ePpEDp
|
||||
ZL4piobs8EIjFcWU2C/8La0rygbcDDqUJkW/nViH0Drs5ctgJl/uYeiDLnZiSZO+
|
||||
PBoOA0trXxqlJPxuEseJbFaLvVE81DsLBnltDPqhJZEUJPEUIjtfStOhQM0f5YoO
|
||||
Greewk7P5LUkTAuTIz+xk6uKnC1YF89SKmdPMH26Rcy6q1IqhEQG3tjUliJZiKS9
|
||||
eQlnp9gskqNGop9gEsmFIMnhlrTUsVU2q38zLDFcmNDbNkRvv4Tf+7jHo4tLs02m
|
||||
/fMTikdMNCRMOcJKbdobcwXq4Ghvc85xeM4/8wIQHEDFbLzmxi/4ibFfJjOfQOe9
|
||||
VpK+TbeSBTxBw42CH9V0TUOz/LELYkMrJ+zfwMCvTV4Eodmc0gjwiy9aIYdpZXKF
|
||||
ueqGGljhVCq2XqkaSATFu5CsdPZrfUszLiAVqpuVTMjy3VQ9ICzz1Bd5ICBRFt6l
|
||||
fDsvfB6SgmB9r1bC+yr/tqe/unzUPajL5mZwn7Jev0uOl6mBNmQpcIEne+9cKedf
|
||||
vPCv/tUQpwpz6blGacEtTxuM+fWDk+CLpPnId/4PEnIQWRFE2aXQ4nrJSwu3O5+k
|
||||
vuZNWety8MOSkuQr0toH//p3VNHOtT7L4JWSlDfTZQIDAQABMA0GCSqGSIb3DQEB
|
||||
CwUAA4ICAQANoLbh4Di1A485ggnKfp9ykfVvg+NduGm+eqr27be033N6IJ7fuKmF
|
||||
7Ki1E+sp0IJDMdyikbqoHdbroHu2chwp/1GzFxhx3Vo7kswS/ehSxWpHjhtrGjAF
|
||||
tSDUFR9q4C+km2A3k1ZNyd9C1w+R9Lr16j9lBQoGWmUgFsRf8ED16FMMSK/1mCC0
|
||||
1VSydYnYhKmPZxMByTozGTV97wihA0XqXtadbkQoyvcnEuarBvEX3mPA3effqcEG
|
||||
rreeDp1yzYtQRW85ZASaqgF5CYKhe9NekurZ5Jd+2mQPYRWPFpIFsSHoOLqbfcN1
|
||||
/S0Si6LZWq0Mi8cbSi/zq+Eh7Q0w+EXP6Goh0A7M4e1dKt+cfGQpeyeg3cF49Cda
|
||||
RrTkSv2Sl2jbrShqAsG/HNhyBaI/gtLGi9tig9wAoV2zjGw1Ehs0GcwFq2g/f6Zv
|
||||
W7rdZIBZT1RIQLczuwsEhv6cFrSM5OU2f5fuKnmeI6uvyz0jRqiMncYRjwI6LotB
|
||||
sWmdTWi6xsctYirJ3Yip5Tqm01asyUuIeiT+eQ6I9CfHLiGBXEzxlK5PrFqfD7gK
|
||||
LfB1gCN7SAlxKGepmFMwfF+fDrsurL2T2ePxaaNBLjRAzmpSpH0NedMd+eanuHXV
|
||||
a/iOL2XmVis7iuFyk5M2XtLFfYBff+mxSPA8d9u5kCpoel0tx3iLmw==
|
||||
-----END CERTIFICATE-----
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue